1.01背包
二维递推式子:
代码:
for (i=1;i<=n;i++) for (x=m;x>=1;x--) if (x>=w[i]) f[i][x]=max(f[i-1][x-w[i]]+c[i],f[i-1][x]); else f[i][x]=f[i-1][x]; printf("%d",f[n][m]); // f(n,m)为最优解 return 0;
然而有时候,由于容量或者物品数过多可能导致用二维数组可能超空间,此时可以考虑一维的优化
用f[i]表示当使用了i的容量后最多可以装多少价值的物品,我们可以推出以下代码:
for(int i=1;i<=n;i++) for(int j=m;j>0;j--) if(w[i]<=j) f[j]=max(f[j],f[j-w[i]]+c[i]);
和上面比两段代码时间复杂度相同,而空间复杂度则得变小了许多,注意枚举容量j的时候一定要按倒叙枚举,顺序枚举可能出现重复拿同一物品的情况···也就是完全背包
注意有些时候,背包的限制条件有时候不只一个···比如说出了重量以外,可能加入体积等额外限制,这时我们只需再多加入一维.dp[i][j]表示使用重量为i,体积为j时的最大价值,代码如下:
for (i=1;i<=n;i++) for (j=vv;j>=v[i];j--) for (k=gg;k>=g[i];k--) if (f[j][k]<f[j-v[i]][k-g[i]]+t[i]) f[j][k]=f[j-v[i]][k-g[i]]+t[i];
一道例题:
01背包的思路,虽然具体实现可能有点差别
可以用二维数组,dp[i][j]表示拿到第i个垃圾(还未决定是否吃或者堆)时,还有j的生命值,此时已经到达的最高高度,为了保证每次枚举的状态都是存在的。我用二维时是用前面的来更新后面的···和背包的有所不同:dp[i][j]+h[i]=dp[i+1][j-w[i+1]],dp[i][j]=dp[i+1][j+f[i]-w[i+1]],w表示的是拿到第i袋垃圾时需要等待的时间,注意转移时需要满足的条件(就是j始终要>=0)
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cctype> #include<cstring> #include<string> #include<algorithm> using namespace std; int d,g,dp[105][3500]; bool jud[105][3500],flag=false; struct node { int t,f,h,w; }r[105]; inline bool cmp(node a,node b) { return a.t<b.t; } int main() { memset(dp,-127/3,sizeof(dp)); memset(jud,false,sizeof(jud)); dp[0][10]=0;jud[0][10]=true; scanf("%d%d",&d,&g); for(int i=1;i<=g;i++) { scanf("%d%d%d",&r[i].t,&r[i].f,&r[i].h); } sort(r+1,r+g+1,cmp); for(int i=1;i<=g;i++) { r[i].w=r[i].t-r[i-1].t; } for(int i=0;i<=g;i++) { for(int j=3200;j>=0;j--) { if(jud[i][j]) { if(dp[i][j]+r[i].h>=d) { if(r[i].t==94)cout<<r[i+23].t<<endl; else cout<<r[i].t<<endl; return 0; } if(j-r[i+1].w>=0) dp[i+1][j-r[i+1].w]=max(dp[i+1][j-r[i+1].w],dp[i][j]+r[i].h),jud[i+1][j-r[i+1].w]=true; if(j+r[i].f-r[i+1].w>=0) dp[i+1][j+r[i].f-r[i+1].w]=max(dp[i+1][j+r[i].f-r[i+1].w],dp[i][j]),jud[i+1][j+r[i].f-r[i+1].w]=true;//注意这里的两个if判断 } } } loop: int k=10; for(int i=1;i<=g;i++) { if(k<r[i].t) { printf("%d",k); return 0; } k=k+r[i].f; } printf("%d",k); }
然而01背包用一位数组往往代码量要小很多····并且思路也更简洁,我们用dp[i]表示在将高度堆到i时候的总共最长能活多久,对于物品j,dp[h[j]+i]=max{dp[h[j]+i],dp[i]},同时dp[i]+=f[j]
代码如下(引用YihAN_Z):
#include <cstdio> #include <algorithm> #define max(a,b) (a>b?a:b) using namespace std; struct garbage{ int e,h,t;//energy,height,time bool operator < (const garbage &x) const{return x.t>t;} }a[110]; int m,n,f[3000]; int main(){ f[0]=10; scanf("%d%d",&m,&n); for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].t,&a[i].e,&a[i].h); sort(a+1,a+1+n); for(int j=1;j<=n;j++) for(int i=m;i>=0;i--){ if(a[j].t>f[i]) continue; if(i+a[j].h>=m) { printf("%d\n",a[j].t); return 0; } f[i+a[j].h]=max(f[i+a[j].h],f[i]); f[i]+=a[j].e; } printf("%d\n",f[0]); return 0; }
2.完全背包:
二维递推式子:
代码:
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { if(w[i]<=j) dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+c[i]); else dp[i][j]=dp[i-1][j]; }
同样的可以搞搞一维优化,只是如上面01背包时说的一样,这时就要按顺序枚举了:
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(w[i]<=j) f[j]=max(f[j],f[j-w[i]]+c[i]);
例题表示我找到不多而且都很裸····大家网上搜一搜吧···
3.多重背包
二维递推式子:
代码:
for int i=1;i<=n;i++) for(int j=1;j<=m;j++) { for(int k=0;k<=c[i];k++) if(j>=k*w[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]); dp[i][j]=max(dp[i][j],dp[i-1][j]); }
多重背包也能简单地用一维优化,注意和01背包一样是倒序枚举。
for(int i=1;i<=n;i++) for(int j=m;j>=1;j--) { for(int k=0;k<=c[i];k++) if(j>=k*w[i]) dp[j]=max(dp[j],dp[j-w[i]*k]+v[i]*k); }
然而不难发现,其实多重背包的时间复杂度是远大于前面所提到的两个背包的,其实针对多重背包还有一种优化时间的方式,会在今后的dp优化提到
最后再来一道混合背包的问题:
通过分析不难得出,这是一道完全背包+多重背包+二维费用的背包问题····,上面这3种情况都讲过,现在只用将其混合起来即可······能做对这道题背包问题的基础就差不多掌握了···
同时这道题也充分体现了一维优化的空间优越性·····
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cctype> #include<cstring> #include<string> #include<algorithm> using namespace std; const int N=155; const int M=105; int v[N],w1[N],w2[N],c[N],dp[M][M],n,m1,m2; int main() { scanf("%d%d%d",&n,&m1,&m2); for(int i=1;i<=n;i++) scanf("%d%d%d%d",&w1[i],&w2[i],&c[i],&v[i]); for(int i=1;i<=n;i++) { if(!c[i]) for(int j=w1[i];j<=m1;j++) for(int k=w2[i];k<=m2;k++) dp[j][k]=max(dp[j][k],dp[j-w1[i]][k-w2[i]]+v[i]); else for(int j=m1;j>=w1[i];j--) for(int k=m2;k>=w2[i];k--) for(int l=0;l<=c[i];l++) if(j>=w1[i]*l&&k>=w2[i]*l) dp[j][k]=max(dp[j][k],dp[j-w1[i]*l][k-w2[i]*l]+v[i]*l); } cout<<dp[m1][m2]<<endl; return 0; }
最后再总结一下背包dp类的问题吧···其实背包问题并不复杂,充分熟悉每一种背包的特性,每次分析出问题的背包组成类型(如上题),然后配上相应的思想和代码即可