Hdu 1709 The Balance
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1709
题意:
给N个整数,每个数只能使用一次。将他们组合起来,最后看在1~sum(a[1]..a[N])这些数里有多少数是这N个数组合不出来的.
先输出这些数的个数,再将这些数输出来。如果个数是0,那么不需要输出数。
案例分析:
input:
3
1 2 4
output:
0
-->1(||4-1-2) , 2(||4-2) , 1+2(||4-1) , 4 , 1+4 , 2+4 , 1+2+4.
思路分析:
可以将这N个数看成是N个物品。就将这个问题转化成了一个0-1背包问题,那么,所有的加法组合就可以求出来。那么减法呢?
依旧是上面的案例,由于这组案例比较特殊。 只需加法组合便可以将1~sum(a[1]..a[N])全部组合出来.不过并不妨碍分析减法组合过程。
0-1背包解法得到的加法组合必然是一个以上物品组合起来的(这个是无需置疑的),那么减法无非是减少组合的物品个数。
如 (4+1)-1=4 --> 4+1的组合减去1的组合 得到了一个新的组合。
看第二个案例或许更明白:
3
9 2 1
先将加法组合用0-1背包解出来:
1 , 2 , 1+2 , 9 , 1+9 , 2+9 , 1+2+9 .
这是所有的加法组合,接下来利用加法求减法组合。
2-1=1 , (1+2)-1=2 , 9-1=8 , (1+9)-1=9 , (2+9)-1=10 , (1+2+9)-1=11.
(1+2)-1=2 , 9-2=7 , (1+9)-2=8 , (2+9)-2=9 , (1+2+9)-2=10.
9-(1+2)=6 , (9+1)-(1+2)=7 , (2+9)-(1+2)=8 , (1+2+9)-(1+2)=9.
...
...
最后所有的减法都暴力扫完之后,发现 4和5这两个数字是组合不出来的。
所以答案便是 2 个数 4和5 了。
代码如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 #define MAX 101 7 int w[MAX*MAX],c[MAX*MAX];//w->价值,c->所需空间花费; 8 bool flag[MAX*MAX]; 9 int f[MAX*MAX]; 10 int x[MAX*MAX]; 11 int p[MAX*MAX]; 12 int n,v,ans; 13 void init() 14 { 15 memset(w,0,sizeof(w)); 16 memset(c,0,sizeof(c)); 17 memset(f,0,sizeof(f)); 18 memset(p,0,sizeof(p)); 19 memset(x,0,sizeof(x)); 20 memset(flag,false,sizeof(flag)); 21 v=0; 22 ans=0; 23 } 24 void out() 25 { 26 for(int i=0;i<=v;i++) 27 cout<<f[i]<<" "; 28 cout<<endl; 29 } 30 void read()//初始化物品信息; 31 { 32 int i; 33 for(i=1;i<=n;i++) 34 { 35 scanf("%d",&w[i]); 36 c[i]=w[i]; 37 v+=w[i]; 38 } 39 } 40 void cal() 41 { 42 int i,j; 43 int k=0; 44 for(i=1;i<=n;i++)//0-1背包 遍历所有加法可能 45 for(j=v;j>=c[i];j--) 46 { 47 if(f[j]>=0) flag[f[j]]=true; 48 f[j]=max(f[j],f[j-c[i]]+w[i]); 49 if(f[j]>=0) flag[f[j]]=true; 50 } 51 //out(); 52 for(i=0;i<=v;i++)//找出所有加法组合; 53 if(flag[i]) x[k++]=i; 54 55 for(i=0;i<k;i++)//暴力找出可能的减法组合; 56 for(j=i+1;j<k;j++) 57 if(x[j]-x[i]>=0) flag[x[j]-x[i]]=true; 58 59 for(i=0;i<=v;i++) 60 if(!flag[i]) p[ans++]=i; 61 62 printf("%d\n",ans); 63 if(!ans) return ; 64 for(i=0;i<ans;i++) 65 printf(i==ans-1?"%d\n":"%d ",p[i]); 66 } 67 void solve() 68 { 69 init(); 70 read(); 71 cal(); 72 } 73 74 int main() 75 { 76 while(scanf("%d",&n)!=EOF) 77 { 78 solve(); 79 } 80 return 0; 81 }
还有一种常见的方法便是母函数~~
是不是很像呢. 把这n个数看成是n克的砝码,每个砝码只有一个。
母函数的思路就不赘诉了 , 不知道的朋友可以百度学习一下。
这里需要改动一点点的便是:在合并的过程中加入减法合并。
只是需要注意 每个砝码只有一个。
代码如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 #define MAX 101 7 int num[MAX]; 8 int c1[MAX*MAX]; 9 int c2[MAX*MAX]; 10 int ans[MAX*MAX]; 11 int n,sum; 12 void init() 13 { 14 sum=0; 15 memset(c1,0,sizeof(c1)); 16 memset(c2,0,sizeof(c2)); 17 } 18 void read() 19 { 20 for(int i=1;i<=n;i++) 21 { 22 scanf("%d",&num[i]); 23 sum+=num[i]; 24 } 25 } 26 void cal() 27 { 28 int i,j,k; 29 int count=0; 30 c1[0]=c1[num[1]]=1; 31 for(i=2;i<=n;i++) 32 { 33 for(j=0;j<=sum;j++) 34 for(k=0;k+j<=sum&&k<=num[i];k+=num[i]) 35 { 36 if(j>=k) c2[j-k]+=c1[j]; 37 else c2[k-j]+=c1[j]; 38 c2[j+k]+=c1[j]; 39 } 40 for(j=0;j<=sum;j++) 41 { 42 c1[j]=c2[j]; 43 c2[j]=0; 44 } 45 } 46 for(i=1;i<=sum;i++) 47 { 48 if(!c1[i]) 49 { 50 ans[count++]=i; 51 } 52 } 53 printf("%d\n",count); 54 if(!count) return ; 55 for(i=0;i<count;i++) 56 printf(i==count-1?"%d\n":"%d ",ans[i]); 57 } 58 void solve() 59 { 60 init(); 61 read(); 62 cal(); 63 } 64 65 int main() 66 { 67 while(scanf("%d",&n)!=EOF) 68 { 69 solve(); 70 } 71 return 0; 72 }