背包系列练习及总结(hud 2602 && hdu 2844 Coins && hdu 2159 && poj 1170 Shopping Offers && hdu 3092 Least common multiple && poj 1015 Jury Compromise)
作为一个oier,以及大学acm党背包是必不可少的一部分。好久没做背包类动规了。久违地练习下-。-
dd__engi的背包九讲:http://love-oriented.com/pack/
鸣谢http://blog.csdn.net/eagle_or_snail/article/details/50987044,这里有大部分比较有趣的dp练手题。
hud 2602 01背包板子题
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define clr(x) memset(x,0,sizeof(x)) 5 using namespace std; 6 struct node 7 { 8 int cost,val; 9 }bag[1010]; 10 int dp[1010]; 11 int main() 12 { 13 int T,n,m; 14 scanf("%d",&T); 15 while(T--) 16 { 17 scanf("%d%d",&n,&m); 18 clr(dp); 19 clr(bag); 20 for(int i=1;i<=n;i++) 21 scanf("%d",&bag[i].val); 22 for(int i=1;i<=n;i++) 23 scanf("%d",&bag[i].cost); 24 for(int i=1;i<=n;i++) 25 for(int j=m;j>=bag[i].cost;j--) 26 { 27 if(dp[j]<dp[j-bag[i].cost]+bag[i].val) 28 dp[j]=dp[j-bag[i].cost]+bag[i].val; 29 } 30 printf("%d\n",dp[m]); 31 } 32 return 0; 33 }
hdu 2844 Coins 多重背包
就是一个10w的多重背包,每个物品的cost同时也作为value去做背包,我们求的是每个容量下的价值,所以没法做背包九讲里提到的对最终目标的常数优化。那么我们做完以后,统计一下背包里dp[i]==i的空间i总数即为答案。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define clr(x) memset(x,0,sizeof(x)) 5 #define MAXN 100010 6 using namespace std; 7 int dp[MAXN]; 8 struct node 9 { 10 int num,cost; 11 }good[MAXN]; 12 int n,m,s,t,l,k,ans; 13 int max(int a,int b) 14 { 15 return a>b?a:b; 16 } 17 void zeroonepack(int cost,int val) 18 { 19 for(int i=m;i>=cost;i--) 20 { 21 dp[i]=max(dp[i],dp[i-cost]+val); 22 } 23 return ; 24 } 25 void completepack(int cost,int val) 26 { 27 for(int i=cost;i<=m;i++) 28 { 29 dp[i]=max(dp[i],dp[i-cost]+val); 30 } 31 return ; 32 } 33 void multipack(int cost,int val,int num) 34 { 35 if(cost*num>=m) 36 { 37 completepack(cost,val); 38 } 39 else 40 { 41 int t=1; 42 while(t<=num) 43 { 44 zeroonepack(t*cost,t*val); 45 num-=t; 46 t*=2; 47 } 48 zeroonepack(num*cost,num*val); 49 } 50 return ; 51 } 52 int main() 53 { 54 while(scanf("%d%d",&n,&m)!=EOF && m!=0 && n!=0) 55 { 56 clr(dp); 57 for(int i=1;i<=n;i++) 58 { 59 scanf("%d",&good[i].cost); 60 } 61 for(int i=1;i<=n;i++) 62 { 63 scanf("%d",&good[i].num); 64 } 65 for(int i=1;i<=n;i++) 66 { 67 multipack(good[i].cost,good[i].cost,good[i].num); 68 } 69 ans=0; 70 for(int i=1;i<=m;i++) 71 { 72 if(dp[i]==i) 73 ans++; 74 } 75 printf("%d\n",ans); 76 } 77 return 0; 78 }
hdu 2159 FATE 完全背包
一个二维完全背包,将消耗忍耐度和杀怪数作为二维的cost,然后将求解每个忍耐度和杀怪数下经验值的最大值作为最大价值。我们做一遍二维完全背包,然后找杀怪数满(即dp[s][i])时最小的经验值超过升级所需经验值n的i,这个i即为最小忍耐度,然后m-i即为答案。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define clr(x) memset(x,0,sizeof(x)) 5 #define MAXN 110 6 using namespace std; 7 int dp[MAXN][MAXN],v[MAXN],c[MAXN]; 8 int n,m,k,s,t,l,ans,minn; 9 int max(int a,int b) 10 { 11 return a>b?a:b; 12 } 13 int main() 14 { 15 while(scanf("%d%d%d%d",&n,&m,&k,&s)!=EOF) 16 { 17 clr(dp); 18 for(int i=1;i<=k;i++) 19 { 20 scanf("%d%d",&v[i],&c[i]); 21 } 22 for(int i=1;i<=k;i++) 23 { 24 for(int j=1;j<=s;j++) 25 { 26 for(int t=c[i];t<=m;t++) 27 dp[j][t]=max(dp[j][t],max(dp[j-1][t],dp[j-1][t-c[i]]+v[i])); 28 } 29 } 30 if(dp[s][m]<n) 31 { 32 printf("-1\n"); 33 } 34 else 35 { 36 minn=m; 37 for(int i=0;i<=m;i++) 38 if(dp[s][i]>=n && i<minn) 39 { 40 minn=i; 41 break; 42 } 43 printf("%d\n",m-minn); 44 } 45 } 46 return 0; 47 }
poj 1170 Shopping Offers 状压+完全背包
最多五个物品,每个物品最多五件。因此可以将每个状态,cost,最终状态压缩成五位的六进制数,然后做求最小值的完全背包就行了。当然这题还是用编号的形式给出物品的,你需要把物品弄成连续的编号再做压缩操作。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define clr(x) memset(x,0,sizeof(x)) 5 #define clrmax(x) memset(x,0x3f3f3f3f,sizeof(x)) 6 #define MAXN 1000010 7 using namespace std; 8 int dp[MAXN]; 9 int v[MAXN],c[MAXN]; 10 int inf[MAXN]; 11 int bit(int n) 12 { 13 int l=1; 14 n--; 15 while(n--) 16 l*=6; 17 return l; 18 } 19 bool infer(int i,int j) 20 { 21 while(j) 22 { 23 if(i%6<j%6) 24 return false; 25 i/=6; 26 j/=6; 27 } 28 return true; 29 } 30 int main() 31 { 32 int n,m,maxn,ans,t,k,l,r,cnt,flag,num; 33 while(scanf("%d",&n)!=EOF) 34 { 35 m=0; 36 clr(inf); 37 clrmax(dp); 38 for(int i=1;i<=n;i++) 39 { 40 scanf("%d",&k); 41 inf[k]=i; 42 c[i]=bit(i); 43 scanf("%d%d",&num,&v[i]); 44 m+=bit(i)*num; 45 } 46 cnt=n; 47 scanf("%d",&k); 48 for(int i=1;i<=k;i++) 49 { 50 scanf("%d",&l); 51 flag=0; 52 r=0; 53 for(int i=1;i<=l;i++) 54 { 55 scanf("%d%d",&n,&t); 56 if(inf[n]==0 || flag==1) 57 { 58 flag=1; 59 continue; 60 } 61 r+=bit(inf[n])*t; 62 } 63 if(flag==0) 64 { 65 c[++cnt]=r; 66 scanf("%d",&v[cnt]); 67 } 68 else 69 { 70 scanf("%d",&r); 71 } 72 } 73 dp[0]=0; 74 for(int i=1;i<=cnt;i++) 75 { 76 for(int j=c[i];j<=m;j++) 77 if(infer(j,c[i])) 78 { 79 dp[j]=min(dp[j],dp[j-c[i]]+v[i]); 80 } 81 } 82 printf("%d\n",dp[m]); 83 } 84 return 0; 85 }
hdu 3092 Least common multiple 转化为01背包
把一个数n进行划分,求最大的lcm,并将n%mod。
我们先不要在意%mod。对于一个数n,我们发觉(只有质数或者相同质数的积和剩下一堆1)的划分的情况下n的lcm会达到最大——因为,在第p次非质数的划分你可以拆解成n个质数相乘,将n个质数或者相同质数的乘积唯一化为k个作为划分后,这k个数的和小于等于该非质数,显然后者能使第p次划分完之后剩下的划分数字更大,得出来的lcm更大。当然剩下的多余的数全部拆解成1,这样对答案是不影响的(没有贡献的)。你可以发觉这很像一个01背包,而且是最大容量为n的背包,而物品则是质数或相同质数的积,cost为该数,value也为该数,那么对于每个状态的转移为:dp[j]=max(dp[j-c[i]]*v[i],dp[j])。并且每个物品只影响一次,即为01背包,毕竟一个质数对该状态的贡献只有乘一次该质数而不能多次。所以加上一些n个相同质数的积的细节处理(想想为什么)后做个01背包,即能求解出答案 dp[n]。
然而相乘数字太大,并且需要mod,我们可以用一个数组dp来存进行特殊处理过的大小以方便进行动态规划,另一个数组ans来存答案,包括%mod。将dp中的数全部log10以表示更大范围,每次转移就变为dp[j]=max(dp[j-c[i]]+log10(v[i]),dp[j])便于比较大小,dp[j]被更新后ans也同样更新,并且%mod。
最后求出来ans[n]即为答案。
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<cstring> 5 #define clr(x) memset(x,0,sizeof(x)) 6 #define MAXN 10010 7 using namespace std; 8 double dp[MAXN]; 9 int ans[MAXN]; 10 int prime[MAXN],inf[MAXN]; 11 int n,m,s,k,t,mod,cnt; 12 double u,v,b,x; 13 void primer(int n) 14 { 15 clr(inf); 16 cnt=0; 17 for(int i=2;i<=n;i++) 18 { 19 if(!inf[i]) 20 { 21 prime[++cnt]=i; 22 inf[i]=1; 23 } 24 for(int j=1;j<=cnt;j++) 25 { 26 if(prime[j]*i>n) break; 27 inf[prime[j]*i]=1; 28 if(i%prime[j]==0) 29 break; 30 } 31 } 32 return ; 33 } 34 int main() 35 { 36 primer(3000); 37 while(scanf("%d%d",&n,&mod)==2) 38 { 39 clr(dp); 40 for(int i=0;i<=n;i++) 41 ans[i]=1; 42 for(int i=1;i<=cnt && prime[i]<=n;i++) 43 { 44 u=log10(prime[i]*1.0); 45 for(int j=n;j>=prime[i];j--) 46 { 47 for(int k=1,t=prime[i];t<=j;k++,t=t*prime[i]) 48 if(dp[j-t]+k*u>dp[j]) 49 { 50 dp[j]=dp[j-t]+k*u; 51 ans[j]=(ans[j-t]*t)%mod; 52 } 53 } 54 } 55 printf("%d\n",ans[n]); 56 } 57 return 0; 58 }
poj 1015 Jury Compromise 区间右移+输出路径
这一题,我们需要得出最小的$ |D[sum]-P[sum]|$的情况下最大的$ |D[sum]+P[sum]|$。
那我们可以考虑做一个二维的01背包,其中背包的两个费用为人数和当前做到第i个人的d[sum]-p[sum]。因为这样会涉及负数,因此将整个区间右移。考虑到$ m\le 20$ 以及 $ -20 \le d[i]-d[j] \le 20 $ 因此区间为[-400,400],将其右移为[0.800]。然后我们要求的是背包在当前费用下最大的d[sum]+p[sum]。在按照d[sum]-p[sum]这个费用循环时注意,背包上限是(400<400+c[i])?400:400+c[i]下限是c[i]-400>-400?c[i]-400:-400。
背包处理完以后,在400附近找最靠近400的i,即最小的|d[sum]-p[sum]|,并且 dp[n][m][400+i]是最大的,然后输出(i+dp[n][m][400+i])/2为P[sum],(i-dp[n][m][400+i])/2为D[sum]。
接下来就是按路径输出的问题了,这个路径背包九讲里有讲过怎么获得我就不再赘言了。但是poj说好的是special judge,然后发觉应该输出最大字典序才行。。坑死了,害我看了好久。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define clr(x) memset(x,0,sizeof(x)) 5 #define clrmin(x) memset(x,-0x3f3f3f3f,sizeof(x)) 6 using namespace std; 7 int dp[210][50][810]; 8 int c[210],v[210],ans[50]; 9 int n,m,k,s,t,l,r; 10 int max(int a,int b) 11 { 12 return a>b?a:b; 13 } 14 int main() 15 { 16 int kase=0,fit; 17 while(scanf("%d%d",&n,&m)!=EOF && n>0 && m>0) 18 { 19 clrmin(dp); 20 dp[0][0][400]=0; 21 for(int i=1;i<=n;i++) 22 { 23 scanf("%d%d",&l,&r); 24 c[i]=l-r; 25 v[i]=l+r; 26 dp[i][0][400]=0; 27 } 28 for(int i=1;i<=n;i++) 29 { 30 for(int j=m;j>0;j--) 31 { 32 for(int k=(400<400+c[i])?400:400+c[i];k>=c[i]-400 && k>=-400;k--) 33 { 34 dp[i][j][k+400]=max(dp[i-1][j][k+400],dp[i-1][j-1][k-c[i]+400]+v[i]); 35 } 36 } 37 /* for(int ii=0;ii<=2;ii++) 38 { 39 for(int jj=398;jj<=406;jj++) 40 printf("%d ",dp[i][ii][jj]>=0?dp[i][ii][jj]:-1); 41 printf("\n"); 42 } */ 43 // printf("%d\n",dp[i][0][400]); 44 } 45 for(int i=0;i<=400;i++) 46 { 47 if(dp[n][m][400+i]>0 || dp[n][m][400-i]>0) 48 { 49 if(dp[n][m][400+i]>dp[n][m][400-i]) 50 l=i; 51 else 52 l=-i; 53 break; 54 } 55 } 56 printf("Jury #%d\n",++kase); 57 printf("Best jury has value %d for prosecution and value %d for defence:\n",(l+dp[n][m][400+l])/2,(dp[n][m][400+l]-l)/2); 58 l=400+l; 59 t=m; 60 for(int i=n;i>=1;i--) 61 if(dp[i][m][l]==dp[i-1][m-1][l-c[i]]+v[i]) 62 { 63 ans[m]=i; 64 m--; 65 l-=c[i]; 66 if(m==0) 67 break; 68 } 69 for(int i=1;i<=t;i++) 70 printf(" %d",ans[i]); 71 printf("\n\n"); 72 } 73 return 0; 74 }
做了这么些题,可以发觉背包题需要对费用很敏感,对于是精确到某个容量下的(即装满)的背包和非精确(即最多为该容量)的背包,赋初值的方式是不同的。还有要善于发掘背包的费用,费用可以是任何可以用来递推的下标。可能是多维(2以上)要擅用状态压缩使得问题简单化。
当然对于dp都有一个共同点:数据范围特别小。注意下数据范围有可能就能发现他是一道dp甚至背包,从而快速解决。