背包系列练习及总结(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 }
01背包

 

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 }
二维01背包

 

做了这么些题,可以发觉背包题需要对费用很敏感,对于是精确到某个容量下的(即装满)的背包和非精确(即最多为该容量)的背包,赋初值的方式是不同的。还有要善于发掘背包的费用,费用可以是任何可以用来递推的下标。可能是多维(2以上)要擅用状态压缩使得问题简单化。

当然对于dp都有一个共同点:数据范围特别小。注意下数据范围有可能就能发现他是一道dp甚至背包,从而快速解决。

posted @ 2017-05-06 15:55  hk_lin  阅读(317)  评论(0编辑  收藏  举报