题目链接
1 0 1 2 0 0 1 0 0 0 1 1 0 0 0 0 0 0
Collection #1: Can't be divided. Collection #2: Can be divided.
题解:
方法一:典型的多重背包问题,可以多重背包O(n*v)的方法来做(用单调队列优化)
#include<stdio.h> #include<iostream> #include<algorithm> #include<math.h> #include<queue> #include<stack> #include<set> #include<map> #include<vector> #include<string.h> #include<string> #include<stdlib.h> typedef long long LL; typedef unsigned long long LLU; const int nn=110000; const int inf=0x3fffffff; const LL inf64=(LL)inf*inf; const int mod=1000000007; using namespace std; int a[10]; int dp[nn*10]; pair<int,int>dq[7][nn*5]; int head[7],last[7]; int main() { int i,j; int cas=1; while(1) { int sum=0; for(i=1;i<=6;i++) { scanf("%d",&a[i]); sum+=i*a[i]; } if(sum==0) break; printf("Collection #%d:\n",cas++); if(sum&1) { puts("Can't be divided."); puts(""); continue; } dp[0]=0; for(i=1;i<=sum/2;i++) { dp[i]=-inf; } pair<int,int>tem; for(i=1;i<=6;i++) { for(j=0;j<i;j++) head[j]=last[j]=0; for(j=0;j<=sum/2;j++) { if(dp[j]!=-inf) { while(head[j%i]<last[j%i]) { tem=dq[j%i][last[j%i]-1]; if(tem.second<=dp[j]-j/i*i) last[j%i]--; else break; } dq[j%i][last[j%i]++]=(make_pair(j/i,dp[j]-j/i*i)); } while(head[j%i]<last[j%i]) { tem=dq[j%i][head[j%i]]; if(tem.first<j/i-a[i]) head[j%i]++; else { dp[j]=max(dp[j],tem.second+j/i*i); break; } } } } if(dp[sum/2]==sum/2) puts("Can be divided."); else puts("Can't be divided."); puts(""); } return 0; }
那么我们可以用 dp[i][j] 表示前i种硬币,组成价值j是否可行,转移就是:
dp[i][j]=dp[i-1][j-k*i]?true:dp[i][j]。0<=k<=a[i] 。
转移和多重背包转移类似,同样我们可以用优化多重背包转移的原理,剩余类优化。由于我们只考虑可行性,所以我们只用维护这一类的可行的最大值,而不用维护单调队列。同时第一维空间也可以优化掉。
代码如下:
#include<stdio.h> #include<iostream> #include<algorithm> #include<map> #include<string.h> #include<vector> #include<math.h> typedef long long LL; typedef unsigned long long LLU; const double eps=1e-8; const int nn=110000; const int inf=0x3fffffff; using namespace std; int a[10]; bool dp[nn*10]; int f[10]; int main() { int i,j; int cas=1; while(1) { int sum=0; for(i=1;i<=6;i++) { scanf("%d",&a[i]); sum+=a[i]*i; } if(sum==0) break; printf("Collection #%d:\n",cas++); if(sum&1) { puts("Can't be divided."); puts(""); continue; } dp[0]=true; for(i=1;i<=sum/2;i++) dp[i]=false; for(i=1;i<=6;i++) { for(j=0;j<i;j++) f[j]=-inf; for(j=0;j<=sum/2;j++) { if(dp[j]) { f[j%i]=j/i; } else if(f[j%i]>=j/i-a[i]) { dp[j]=true; } } } if(dp[sum/2]) puts("Can be divided."); else puts("Can't be divided."); puts(""); } return 0; }方法三:相对于多重背包而言,此题的价值和体积相等,利用这个性质。
我们用 dp[i][j]表示前i种硬币,组成价值j,最多剩了多少个i物品。转移就是
如果dp[i-1][j]可行,则:
dp[i][j]=ni ;
如果dp[ i ][ j-i ]>=1,则:
dp[i][j]=dp[i][j-i]-1;
代码如下
#include<stdio.h> #include<iostream> #include<algorithm> #include<map> #include<string.h> #include<vector> #include<math.h> typedef long long LL; typedef unsigned long long LLU; const double eps=1e-8; const int nn=110000; const int inf=0x3fffffff; using namespace std; int a[10]; int dp[nn*10]; int main() { int i,j; int cas=1; while(1) { int sum=0; for(i=1;i<=6;i++) { scanf("%d",&a[i]); sum+=a[i]*i; } if(sum==0) break; printf("Collection #%d:\n",cas++); if(sum&1) { puts("Can't be divided."); puts(""); continue; } dp[0]=0; for(i=1;i<=sum/2;i++) dp[i]=-1; for(i=1;i<=6;i++) { for(j=0;j<=sum/2;j++) { if(dp[j]!=-1) { dp[j]=a[i]; } else if(j-i>=0&&dp[j-i]>=1) { dp[j]=dp[j-i]-1; } } } if(dp[sum/2]!=-1) puts("Can be divided."); else puts("Can't be divided."); puts(""); } return 0; }