背包 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\) 背包一样。

例题:[HEOI2013]Eden 的新背包问题 我的题解

单调队列/单调栈优化:

通过单调队列,降低一维的搜索时间,因为单调队列是 \(O(n)\) 的算法。

基本形态: 当前状态的所有值可以从上一个状态的某个连续的段的值得到,要对这个连续的段进行 \(RMQ\) 操作,相邻状态的段的左右区间满足非降的关系。

实在不行暴力也可以....

例题:CF372C Watching Fireworks is Fun 我的题解

单调队列优化多重背包

朴素的状态转移方程:

\[f_{i,j}= \max\limits^{k_i}_{k=0}(f_{{i-1},{j-k*w_i}}+v_i*k) \]

时间复杂度为 \(O(m\sum k_i)\)

考虑优化 \(f_i\) 的转移,设 \(g_{x,y}=f_{{i-1},{j-k*w_i}}\),继续设:

\[G_{x,y}=f_{{i-1},{x*w[i]+y}}-v_i * x \]

则方程可以表示为:

\[g_{x,y}= \max\limits^{k_i}_{k=0}(G_{{x-k},{y}})+v_i * x \]

这样就可以单调队列对 \(G\) 进行优化,时间复杂度降为 \(O(nW)\)

混合背包:

就是将前面三种的背包问题融合起来,合并就行。

二维费用背包:

就是多开一维数组记录一下另外一组费用,和平时的背包问题解决方法一样。

尽量减少维数即可。

例题:P1855 榨取kkksc03

分组背包:

就是将物品分组,每组的物品相互冲突,最多只能选一种物品。

其实就是从 在所有物品中选择一件 变成 从当前组选择一件,对每一组进行 \(0/1\) 背包即可。

例题:P1757 通天之分组背包

#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\) 有以下可能性:

  1. 只选择主件
  2. 选择主件,再选择一个附件
  3. 选择主件,再选择....个附件

需要将以上可能性的容量和价值转化成一件件物品,因为这几种可能性只能选一种,可以看成分组背包。

读入时,如果是附件,就把其定义为背包主件的一部分,计算值,然后在状态转移中,枚举选择情况。

如果是多叉树的集合,则先算子节点,再算父节点。

例题:[NOIP2006 提高组] 金明的预算方案

代码链接:

输出方案:

记录下来背包的某一个状态是怎么推导出来的。

我们用 \(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\) 或者背包九讲继续看

posted @ 2021-09-26 19:26  Evitagen  阅读(201)  评论(0编辑  收藏  举报