详细的背包九讲:
http://www.cnblogs.com/jbelial/articles/2116074.html
01背包:
给你一个容量为V的背包,给你n给物品,每个物品的大小为c【i】,价值为v【i】且每个物品只能取一次,求背包能装的物品的总价值最大为为多少
状态转移方程: dp[i][v]= max(dp[i-1][v], dp[i-1][v- c[i]] + v[i])
在遍历v的时候逆序遍历可以节省空间
完全背包:
每个物品可以取无数次
状态转移方程:
dp[i][v]= max(dp[i-1][v-k*c[i]] + k*v[i]) k>=0 && k*c[i]<= j
在遍历v时从小到大遍历可节约空间和时间
多重背包:
可转化为01背包求解
二维费用背包:
一个物品有两种花费,两种花费都不越界且某种话费最小
状态转移方程:dp【i】【u】【v】= dp【i-1】【u- a【i】】【v-b【i】】+ w【i】
如果是01背包,那么逆序遍历两个花费节约空间即可,完全背包则顺序遍历节约空间和时间
其实二维背包只要在一维状态上面加一维即可,其他照旧
求第K优解的背包:
只需在dp[i][v]上加一维状态,dp[i][v][k] 表示dp[i][v]下面的第k优解,以01背包为例,因为dp[i][v]= max(dp[i-1][v], dp[i-1][v-a[i]]+ b[i]), 所以求第k优解,我们只需要把dp[i][v][1,2..k]和
dp[i-1][v-a[i]][1,2..k] + b[i] 的前k个数存到dp[i][v][1,2,...k]中即可
hdu 1203 I NEED A OFFER! (01背包)
这题就是一个裸的01背包,但是我方向我在写01背包时总是想当然把边界当成1,结果这题的边界是0。。。。
看来太久没写背包越来越差了。。。
代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define maxn 11111 double dp[maxn], b[maxn]; int a[maxn]; double Max(double x, double y) { return x> y? x: y; } int main() { int n, m; while(scanf("%d %d",&n,&m)!=EOF) { if(n== 0 && m== 0) break; for(int i= 1; i<= m; i++) scanf("%d %lf",&a[i],&b[i]); for(int i= 1; i<= m; i++) { for(int j= n; j>= 0; j--) if(j>= a[i]) // 感觉以后遍历的时候最好写成for(int j= n; j>= a[i]; j--) 这样既不用判断,还不用担心边界搞错 { double t= 1.0- (1.0- dp[j-a[i]]) *(1.0- b[i]); dp[j]= Max(dp[j], t); } } printf("%0.1lf%%\n",dp[n]*100); } return 0; }
hdu 1171 Big Event in HDU (01背包)
题意:
给你n种物品,每种物品的个数为ai,价值为bi,把所有的物品均分成两份使得两份的价值差尽量少,且第一份的价值大于或等于第二份的价值
我记得很久之前LSS问过我一个问题,给你n个正数,每个数可以取正或者取反,求这n个数之和离0最近的和为多少
当时我想了一下,觉得把这n个数从小到大排序一次即可,然后把这n个数分成两堆,设第一堆的和为A,第二堆和为B
则分的方法如下:
从最大的数开始遍历,
当A<= B 时,把当前遍历的数ai放到第一堆中,即A+= ai
否则B+= ai
当时帮他测了几组简单的数据发现対了,然后以为这种方法时是対的,这题和他问的问题其实是一样的,所以我最开始没用背包写,就用这种方法写的。。。
然后就一直WA, 然后我发现这种方法完全是错的
例如有七个物品,价值分别为4,4,4,3,3,3,3 时,这种方法分成两堆的结果为14, 11 但很明显这个的正确结果为12 12
这种题目的正确解法应该是01背包,设所有物品的总价值为sum,应该判断离sum/2 最近的合法价值为多少, 结果为sum-ans,ans
同理 LSS问我的那题就应该找出离数字总和的一半最近的合法和
PS:
如果是N个物品,两人轮流来选的话,就只需要再加一维选的数字的数目即可,然后再在dp[k][n/2]中选出离sum/2最近的k即可
hdu 1171 代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> using namespace std; #define maxt 511111 int a[maxt]; int dp[maxt]; int main() { //freopen("in","r", stdin); //freopen("out1","w", stdout); int n; while(scanf("%d",&n)!=EOF && n>= 0) { int tot= 0; int xx, yy, sum= 0; for(int i= 1; i<= n; i++) { scanf("%d %d",&xx,&yy); for(int j= 1; j<= yy; j++) a[++tot]= xx; sum+= xx* yy; } // for(int i= 1; i<= tot; i++) // printf("%d %d\n",i, a[i]); memset(dp, 0, sizeof dp ); dp[0]= 1; for(int i= 1; i<= tot; i++) for(int j= sum/2; j>= a[i]; j--) if(dp[j- a[i]]) dp[j]= 1; int ans= 0 ; for(int i= sum/2; i>= 0; i--) if(dp[i]) { ans= i; break; } printf("%d %d\n",sum- ans, ans); } return 0; }
代码:
hdu 2159 FATE (二维花费的完全背包)
状态转移方程: dp【i】【u】【v】= dp【i-1】【u- k*a【i】】【v-k*b【i】】+ k*w【i】 (k>= 0 && k*a[i]<=u && k*b[i]<= v)
同样这题也木有一下就AC,还错了几次,首先我没优化时间,直接就写了四重循环,因为这是个完全背包,顺序遍历两个花费即可减少一重循环,(这里TLE了一次)
后面改了之后WA 才发现我把两个花费搞反了。。
代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define maxn 111 int dp[maxn][maxn], a[maxn], b[maxn]; int main() { int n, m, k, s; // freopen("in","r",stdin); //freopen("out1","w", stdout); while(scanf("%d %d %d %d",&n,&m,&k,&s)!=EOF) { for(int i= 1; i<= k; i++) scanf("%d %d",&a[i], &b[i]); memset(dp, 0, sizeof(dp)); for(int i= 1; i<= k; i++) for(int j= 1; j<= m; j++) for(int u= 1; u<= s; u++) if(j>= b[i]) dp[j][u]= max( dp[j][u], dp[ j- b[i] ] [u-1] + a[i] ); int ans= -1; for(int i= 1; i<= m; i++) for(int j= 1; j<= s; j++) if(dp[i][j]>= n) ans=max(ans, m-i); printf("%d\n",ans); } return 0; }
hdu 2110 Crisis of HDU (求方案总数)
其实求方案总数的背包和求最大价值是差不多的,只需要改一下状态转移方程即可
dp[j][v]= sum(dp[j][v- k*a[i]]) (k*a[i]<= v)
代码:
#include <cstdio> #include <cstring> #include <cmath> #define maxn 1111 #define maxm 11111 #define MOD 10000 int dp[maxm], d[maxm]; int a[maxn], b[maxn]; int main() { //freopen("in", "r", stdin); //freopen("out1", "w", stdout); int n; while(scanf("%d",&n)!=EOF && n) { int ssum= 0; for(int i= 1; i<= n; i++) { scanf("%d %d",&a[i],&b[i]); ssum+= a[i]*b[i]; } memset(dp, 0, sizeof dp); memset(d, 0, sizeof d); dp[0]= d[0]= 1; for(int i= 1; i<= n; i++) for(int j= ssum; j>= a[i]; j--) for(int k= 1; k<= b[i]; k++) if(j>= k*a[i] && d[j-k*a[i]]) { dp[j]= (dp[j]+ dp[ j- k*a[i] ]) %MOD; d[j]= 1; } // printf("%d\n",ssum); if(ssum%3== 0 && d[ssum/3]) printf("%d\n",dp[ssum/3]); else printf("sorry\n"); } return 0; }
hdu 2639 Bone Collector II (求01背包的第k优解)
在对两个序列合并的时候注意把两个序列要拉出来,不然直接会出错,另外需注意这题的k优解在不同的选取物品,同样的价值的情况下算同解,所以我们在统计k个解的时候还需要把价值一样的解剔除出去
代码:
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; int dp[1111][33]; int w[111], c[111]; int main() { int T; scanf("%d",&T); while(T--) { int n, v, k; scanf("%d %d %d",&n,&v,&k); for(int i= 1; i<= n; i++) scanf("%d",&w[i]); for(int i= 1; i<= n; i++) scanf("%d",&c[i]); memset(dp, 0, sizeof dp); for(int i= 1; i<= n; i++) for(int j= v; j>= c[i]; j--) { int A[33], B[33]; memset(A, 0, sizeof A); memset(B, 0, sizeof B); for(int t= 1; t<= k; t++) A[t]= dp[j][t], B[t]= dp[j-c[i]][t] + w[i]; //将两个序列拉出来合并,直接合并会出错,因为dp[v][x]改变的时候,dp[v-1][x]也改变 int a= 1, b= 1,u= 1; while((a<=k || b<= k)&& u<= k) { if( (b> k) || (a<= k && A[a]> B[b])) { if(A[a]!= dp[j][u-1]) dp[j][u++]= A[a]; a++; } else { if(B[b]!= dp[j][u-1]) dp[j][u++]= B[b]; b++; } } } printf("%d\n",dp[v][k]); } return 0; }