动态规划--背包
1. 01背包:有 N 件物品和一个容量为 V 的背包。第 i 件物品的费用是 c[i],价值是 w[i]。求解将哪些物品装入背包可使价值总和最大。
对于这类问题我们我们定义f[i][j]表示在前i个物品中选总容量为j所能得到的最大价值为多少于是我们状态转移便是这样 f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]);
int f[N][M]; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) for(int j=w[i];j<=m;j++) f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[n][i],ans); printf("%d\n",ans); }
这样一来,我们的时间复杂度就是O(nm),空间复杂度为O(nm),但我们发现f[i]的值只与f[i-1]有关,此时我们可以用滚动数组来优化空间到O(m),为f[j]=max(f[j],[j-w[i]]+v[i]);此时我们的j就要倒序枚举,因为j只会从比它小的j那转移。
int f[M]; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); }
2.完全背包问题:有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。第 i 种物品的费用是 c[i],价值是 w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。这个问题非常类似于 01 背包问题,所不同的是每种物品有无限个。
这时我们就有一个思路,我们将01背包倒序的滚动数组做法正序,这样我们就起到了一个物品选多次的效果
int f[M]; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) for(int j=w[i];j<=m;j++) f[j]=max(f[j],f[j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); }
3.多重背包问题:有 N 种物品和一个容量为 V 的背包。第 i 种物品最多有 n[i]件可用,每件费用是 c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
若是我们将东西直接拆成一个一个,再跑01背包,这势必会超时,这时我们要用到2进制的思想,因为任何一个数都可以拆成由1,2,4,8..因为他们的二进制是01的
int f[M]; void work() { memset(f,0,sizeof(f)); for(int i=n;i>=1;i--) { --g[i]; for(int b=2;b<=g[i];b<<=1) { g[i]-=b; w[++n]=w[i]*b; v[n]=v[i]*b; } if(g[i]!=0) { w[++n]=w[i]*g[i]; v[n]=v[i]*g[i]; } } for(int i=1;i<=n;i++) for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); }
4.混合三种背包问题:如果将三种背包问题混合起来。也就是说,有的物品只可以取一次(01 背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?
01 背包与完全背包的混合考虑只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是 O(nm)。
int f[M]; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) if(g[i]) for(int j=w[i];j<=m;j++) f[j]=max(f[j],f[j-w[i]]+v[i]); else for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); }
如果是再加上多重背包,再加上有的物品最多可以取有限次,我们就只能拆分物品
5.二维费用的背包问题:二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。
费用加了一维,只需状态也加一维即可。设 f[i][v][u]表示前 i 件物品付出两种代价分别为 v和 u 时可获得的最大价值。状态转移方程就是:f[i][v][u]=max(f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i])
6.分组的背包问题:有 N 件物品和一个容量为 V 的背包。第 i 件物品的费用是 c[i],价值是 w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前 k 组物品花费费用 v 能取得的最大权值,则有:f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于第k 。
int f[M]; void work() { memset(f,0,sizeof(f)); for(int k=1;k<=n;k++) for(int i=1;i<=w[k][0];i++) for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[k][i]]+v[k][i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); }
7.有依赖的背包问题:这种背包问题的物品间存在某种“依赖”的关系。也就是说,i 依赖于 j,表示若选物品 i,则必须选物品 j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品
考虑到所有这些策略都是互斥的(也就是说,你只能选择一种策略),所以一个主件和它的附件集合,实际上对应于分组的背包问题中的一个物品组,每个选择了主件又选择了若干个附件的策略对应于这个物品组中的一个物品,其费用和价值都是这个策略中的物品的值的和。但仅仅是这一步转化并不能给出一个好的算法,因为物品组中的物品还是像原问题的策略一样多。
较一般的问题更一般的问题是:依赖关系以图论中“森林”的形式给出(森林即多叉树的集合),也就是说,主件的附件仍然可以具有自己的附件集合,限制只是每个物品最多只依赖于一个物品(只有一个主件)且不出现循环依赖。解决这个问题仍然可以用将每个主件及其附件集合转化为物品组的方式。唯一不同的是,由于附件可能还有附件,就不能将每个附件都看作一个一般的01背包中的物品了。若这个附件也有附件集合,则它必定要被先转化为物品组,然后用分组的背包问题解出主件及其附件集合所对应的附件组中各个费用的附件所对应的价值。事实上,这是一种树形 DP,其特点是每个父节点都需要对它的各个儿子的属性进行一次 DP 以求得自己的相关属性。这已经触及到了“泛化物品”的思想。
8.泛化物品:考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念。详细的来说的话,就是对于每一个物品,我们都一个关于重量和其价值的函数。于是我们每次合并两个泛化的物品,直至只剩一个
int f[N<<1][M],cnt=n; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) for(int k=m;k>=w[i];k--) f[cnt+1][j]=max(f[cnt+1][j],f[cnt][j-w[k]]+v[i][k]); for(int i=1;i<=m;i++) ans=max(f[n+n-1][i],ans); printf("%d\n",ans); }
总代码如下:
#include <cstdlib> #include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> #define lson l,mid,o<<1 #define rson mid+1,r,o<<1|1 using namespace std; typedef long long ll; inline int read() { int a=0,p=0;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-') ch=getchar(); if(ch=='-') p=1,ch=getchar(); while(ch>='0'&&ch<='9') a=(a<<3)+(a<<1)+ch-'0',ch=getchar(); return p?-a:a; } const int N=100,M=1000; int v[N],w[N],g[N],ans=0,n,m; namespace _01_bei_bao1 { int f[N][M]; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) for(int j=w[i];j<=m;j++) f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[n][i],ans); printf("%d\n",ans); } } namespace _01_bei_bao2 { int f[M]; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); } } namespace _wq_bei_bao { int f[M]; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) for(int j=w[i];j<=m;j++) f[j]=max(f[j],f[j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); } } namespace _dc_bei_bao { int f[M]; void work() { memset(f,0,sizeof(f)); for(int i=n;i>=1;i--) { --g[i]; for(int b=2;b<=g[i];b<<=1) { g[i]-=b; w[++n]=w[i]*b; v[n]=v[i]*b; } if(g[i]!=0) { w[++n]=w[i]*g[i]; v[n]=v[i]*g[i]; } } for(int i=1;i<=n;i++) for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); } } namespace _hh_bei_bao1 { int f[M]; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) if(g[i]) for(int j=w[i];j<=m;j++) f[j]=max(f[j],f[j-w[i]]+v[i]); else for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); } } namespace _fz_bei_bao { int f[M]; void work() { memset(f,0,sizeof(f)); for(int k=1;k<=n;k++) for(int i=1;i<=w[k][0];i++) for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[k][i]]+v[k][i]); for(int i=1;i<=m;i++) ans=max(f[i],ans); printf("%d\n",ans); } } namespace _fh_bei_bao { int f[N<<1][M],cnt=n; void work() { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) for(int k=m;k>=w[i];k--) f[cnt+1][j]=max(f[cnt+1][j],f[cnt][j-w[k]]+v[i][k]); for(int i=1;i<=m;i++) ans=max(f[n+n-1][i],ans); printf("%d\n",ans); } } int main() { // freopen(".in","r",stdin); // freopen(".out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;i++) v[i]=read(),w[i]=read(),g[i]=read(); // _01_bei_bao1::work(); // _01_bei_bao2::work(); // _wq_bei_bao::work(); // _dc_bei_bao::work(); // _hh_bei_bao1::work(); // _fz_bei_bao::work(); // _fh_bei_bao::work(); return 0; } /* */