背包 DP 终极总结
背包 \(dp\)
定义:
选择一组有每一组的贡献值,有选择的上限,询问贡献值的题目
分类:
\(0/1\) 背包:
这里需要注意一下的就是节省一维,需要倒序枚举的 \(O(nw(背包上限))\) 的写法:
for(int i=1;i<=n;i++)
for(int j=W;j>=w[i];j--)
if(dp[j-w[i]]+v[i]>dp[j]) dp[j]=dp[j-w[i]]+v[i];
完全背包:
可以重复枚举,因此是正序的写法:
for(int i=1;i<=n;i++)
for(int j=w[i];j<=W;j++)
if(dp[j-w[i]]+v[i]>dp[j]) dp[j]=dp[j-w[i]]+v[i];
多重背包
也是 \(0/1\) 背包的变式,每种物品 \(k_i\) 个。
我们把它转换成 \(0/1\) 背包即可,每个物品分开计算。
二进制分组优化:
把多个物品拆成:
\(1,2,4,8...(n-?)\) 个物品的加和形式,避免进行重复工作.
过程还是跟 \(0/1\) 背包一样。
单调队列/单调栈优化:
通过单调队列,降低一维的搜索时间,因为单调队列是 \(O(n)\) 的算法。
基本形态: 当前状态的所有值可以从上一个状态的某个连续的段的值得到,要对这个连续的段进行 \(RMQ\) 操作,相邻状态的段的左右区间满足非降的关系。
实在不行暴力也可以....
例题:CF372C Watching Fireworks is Fun 我的题解
单调队列优化多重背包:
朴素的状态转移方程:
时间复杂度为 \(O(m\sum k_i)\)
考虑优化 \(f_i\) 的转移,设 \(g_{x,y}=f_{{i-1},{j-k*w_i}}\),继续设:
则方程可以表示为:
这样就可以单调队列对 \(G\) 进行优化,时间复杂度降为 \(O(nW)\) 。
混合背包:
就是将前面三种的背包问题融合起来,合并就行。
二维费用背包:
就是多开一维数组记录一下另外一组费用,和平时的背包问题解决方法一样。
尽量减少维数即可。
分组背包:
就是将物品分组,每组的物品相互冲突,最多只能选一种物品。
其实就是从 在所有物品中选择一件 变成 从当前组选择一件,对每一组进行 \(0/1\) 背包即可。
#include<bits/stdc++.h>
using namespace std;
const int N=10005;
int n,m,T;
int w[N],v[N],dp[N],b[N];
int g[305][305];
int main(){
cin>>n>>m;
for(int i=1,x;i<=m;i++){
scanf("%d%d%d",&w[i],&v[i],&x);
T=max(T,x); b[x]++;//所在的组的物品数
g[x][b[x]]=i;//小组i中第j个物品的标号
}
for(int i=1;i<=T;i++)
for(int j=n;j>=0;j--)
for(int k=1;k<=b[i];k++)//记录标号的对应的值和大小
if(j>=w[g[i][k]])
dp[j]=max(dp[j],dp[j-w[g[i][k]]]+v[g[i][k]]);
cout<<dp[n]<<endl;
system("pause");
return 0;
}
有依赖的背包:
如果选第 \(i\) 个,就必须选第 \(j\) 个,保证不会循环引用。
不依赖别的物品的物品,称为主件。否则称为辅件。
对于 \(dp\) 有以下可能性:
- 只选择主件
- 选择主件,再选择一个附件
- 选择主件,再选择....个附件
需要将以上可能性的容量和价值转化成一件件物品,因为这几种可能性只能选一种,可以看成分组背包。
读入时,如果是附件,就把其定义为背包主件的一部分,计算值,然后在状态转移中,枚举选择情况。
如果是多叉树的集合,则先算子节点,再算父节点。
输出方案:
记录下来背包的某一个状态是怎么推导出来的。
我们用 \(g[i][v]\) 表示第 \(i\) 件物品占用空间为 \(v\) 时是否选择了此物品,然后在转移时记录选用了哪一种策略。
伪代码:
int v = V; // 记录当前的存储空间
// 因为最后一件物品存储的是最终状态,所以从最后一件物品进行循环
for (从最后一件循环至第一件) {
if (g[i][v]) {
选了第 i 项物品;
v -= 第 i 项物品的价值;
} else
未选第 i 项物品;
}
求方案数:
对于给定的一个背包容量、物品费用、其他关系等的问题,求装到一定容量的方案总数。
这种问题就是把求最大值换成求和即可。
就是 \(dp[i]=\sum (dp[i],dp[i-c[i]])\) ,初始为 \(dp[0]=1\).
求最优方案总数:
这里的最优方案是指物品总价值最大的方案。以 \(01\) 背包为例。
结合求最大总价值和方案总数两个问题的思路,最优方案的总数可以这样求:
\(dp[i][v]\) 意义同之前,\(g[i][v]\) 表示这个子问题的最优方案的总数
则在求 \(dp[i][v]\) 的同时求 \(g[i][v]\) 的伪代码如下:
for(int i=1;i<=n;i++)
for(int i=0;i<=V;i++){
f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i]);
g[i][v]=0;
if(f[i][v]==f[i-1][v])
g[i][v]+=g[i-1][v];
if(f[i][v]==f[i-1][v-c[i]]+w[i])
g[i][v]+=g[i-1][v-c[i]];
}
差不多就这么多了.....更多可以到 \(OI-WIKI\) 或者背包九讲继续看