划分问题(dp)

Description

给定一个正整数的集合A={a1,a2,….,an},是否可以将其分割成两个子集合,使两个子集合的数加起来的和相等。例A = { 1, 3, 8, 4, 10} 可以分割:{1, 8, 4} 及 {3, 10}

Input

第一行集合元素个数n  n <=300 第二行n个整数

Output

如果能划分成两个集合,输出任意一个子集,否则输出“no”

Sample Input

5
1 3 8 4 10

Sample Output

3 10

思路:定义一个c[i][j]二维数组,如果 c[i][j]为真则表示{a1,a2,……a[i]}存在子集和为j.j=0时为真。例如上面的样例,写出来就是

i i j0 1 2 3 4 5 6 7 8 9 10 11 12 13
1 1 T T                        
3 2 T T   T T                  
8 3 T T   T T       T T   T T  
4 4 T T   T T T   T T T   T T T
10 5 T T   T T T   T T T T T T T

发现当上一个为真时,其下面一个一定为真,而且加上这个值 a[i]也一定为真。即if(c[i-1][j]==1) c[i][j+a[i]]=1,c[i][j]=1;

那写完这个表格怎么找到这个序列呢,我们知道c[i][j]为真可能从两个方向中来,c[i-1][j]和c[i-1][j-a[i]],所以当c[i-1][j]为假时我们就输出a[i]即可。具体看代码。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
#define LL long long
bool c[350][100100];
int a[350];
int main()
{
    int n,sum=0;
    cin>>n;
    memset(c,0,sizeof(c));
    for(int i=0; i<=n; i++)//将j==0赋为真
        c[i][0]=1;
    for(int i=1; i<=n; i++)
    {
        cin>>a[i];
        sum+=a[i];
    }
    if(sum%2!=0)//不能分成两半就直接输出no
        cout<<"no"<<endl;
    else
    {
        for(int i=1; i<=n; i++)
            for(int j=0; j<=sum/2; j++)
                if(c[i-1][j]==1)
                    c[i][j+a[i]]=1,c[i][j]=1;//根据退出来的公式
        if(c[n][sum/2]==0)//不存在这样的结构就输出no
            cout<<"no"<<endl;
        else{
            int s=sum/2,e=0;
            for(int i=n;i>=1;i--){
                if(c[i][s]==1&&c[i-1][s]!=1){
                    if(e==0)
                        cout<<a[i];
                    else
                        cout<<" "<<a[i];
                    e++;
                    s-=a[i];//将和减去刚刚输出的
                }
            }
            cout<<endl;
        }
    }
    return 0;
}
发布了350 篇原创文章 · 获赞 715 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/ZCY19990813/article/details/101380931