背包问题动态规划

1.01背包问题,有N件物品,背包容量为V,第i件物品占容量c[i],价值是w[i],求装入哪些物品背包的价值最大?每个物品只能放一次或零次。

动态规划思想很简单解决,用一个数组f[i][v]来表示前i件物品,容量为v的最大价值,那么推出转移方程f[i][v] = max(f[i - 1][v],f[i-1][v - c[i] ] + w[i]),然后以i...N,0...V循环即可,时间复杂度O(n2)。那么其实空间复杂度可以在优化,因为f[i][v]可能是由f[i-1][v],f[i-2][v]....推出来的,那么我们最后也只是要求f[N][0...V]的最大值,那么其实f[N-1....0][0....V]是没用的,那么我们就可以只用一个f[v]就可以实现不断滚动更新。用一个一维f[v]表示无论你几件物品,容量为v的时候最大的价值。f[v] = max(f[v],f[v - c[i] ] + w[i] )。然后循环是以i...N,V...0循环,这里以V推到0是因为推f[v]时候,如果要用到f[v - c[i] ](因为要比较f[v]和f[v - c[i] ] + w[i]谁大保留谁),那么从V向0推循环f[v - c[i] ]保存的就是f[i - 1][v - c[i] ]的意义,否则从0向V推f[v - c[i] ]已经更新了,表示的意义就是f[i][v - c[i] ],意义有误。

2.完全背包问题,有N件物品,背包容量为V,第i件物品占容量c[i],价值是w[i],求装入哪些物品背包的价值最大?每个物品可以放无限次。

从01背包二维考虑,只是多了一层循环表示每个物品可以0...K次选取,那么就是for 0...N,for 0...V,for k ( 0 <= k * c[i] <= v) ,然后转移方程f[i][v] = max(f[i-1][v - k * c[i] ] + k * w[i]),然后考虑一维表示,一维空间时间都优化了,因为引入了forK所以每个状态的时间不再是常数,是O(v/c[i])。

一维的转移方程是f[v] = max(f[v],f[v - c[i] ] + w[i]),这与01背包相比是一样的,不同的是循环次序for 0...N,for 0...V,原因是01背包是按照v = V...0逆序来循环是为了保证第i次循环f[i][v]是由f[i-1][v - c[i] ]推出来的,也就是f[v]由f[i - 1][v - c[i] ]推,而不是f[i][v - c[i] ]推,保证在v改变前,v前面的(也就是v - c[i])还保持上一轮的状态,而完全背包恰好利用这一特点,可选择无限件,正需要当考虑f[v]时候是由已经选入第i种物品的f[i][v - c[i] ],而这里巧妙的是经过一轮0...V恰好就可以装k个c[i],价值上升k个w[i]。当0时装不了这个c[i]直到一个t值可以装c[i],然后又增长,又装入一个c[i],到最后V的时候就是可以装入k个c[i]的时候了。

3.多重背包问题,有N件物品,背包容量为V,第i件物品最多有n[i]件可以使用,占容量c[i],价值是w[i],求装入哪些物品背包的价值最大?第i件只能装n[i]次。

在01背包的基础上在加一次循环即可,也就是for 0...N,for V...0 f[v] = max(f[[v] , f[v - c[i] ] + w[i] , f[v - 2 * v[i] ] + 2 * w[i] .... f[v - k * v[i] ] + k * w[i] ),直接给出一维的f代码就是for 0...N,for V...0,for (k <= n[i] && k * c[i]  <=v ) f[v] = max(f[v] , f[v - k * c[i] ] + k * w[i] )。复杂度是O(v * \sumn[i])。

4.多重背包问题优化。由于复杂度较高,优化思路为:将第i种物品拆开,拆成n[i]个,就转化成了01背包问题,但复杂度仍没改变,于是继续优化,采用二进制思想,把第i种物品的n[i]拆成若干物品,拆成(1,2,4,...,2^(k-1),n[i] - 2^(k-1))这样可以组成小于n[i]的任何数。列如,n[i] = 13,拆成1,2,4,6这样可以组合成任一小于13的数字,拆成了log2n[i]个物品,复杂度降为O(V * \sumlogn[i])01背包问题。

5.上述问题及优化都比较简单,下面的究极优化难度可以降低复杂度O(VN)也是主要介绍的。九讲里面也提到男人八题里的一道,有时间把其他的题解也写一下。看第三题,无疑就是f[0]  f[1]  f[2]  ....f[V]不断循环,那么对于一件物品v,f[j]就是求 f[j - v] + w ,f[j - 2 * v] + 2 * w ......f[j - k * v] + k * w的最大值,那么清楚的j % v 是和(j - v) % v .... (j - 2*v) % v.....(j - k*v) % v是相等的,后面我们就可以使用单调队列的思想了,因为在0...v之间的j % v都是不同的,所以问题就转化成循环0 ... v对每一个余数不同的使用一个单调队列优化,平摊复杂度。那么那么对于前面j比较小,甚至都没办法装下一个v,直到+一个v后,容纳的k长度的队列,后面不断给j增加v,那么就想象成优先队列问题,给j增加v,每次取出队首,也就是最大值,更新f[j],然后剔除队列中元素。但这个f[j]其实是变化的,对于下一波循环f[j + v]其实是f[j] +  w   f[j - v] + 2*w.....的最大值,但每个f[j],f[i - v]其实都只加了一个w,那么对于比较而言是不变的,我们只要给每一个f[j]减去相应的w即可。

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
 
using namespace std;
 
const int N = 200010;
 
int f[N],g[N],q[N],v[N],w[N],s[N];
int n,m;
 
int main()
{
    cin >> n >> m;
    for (int i = 1;i <= n;i ++ ) cin >> v[i] >> w[i] >> s[i];
    
    for (int i = 1;i <= n;i ++ )
    {
        memcpy(g,f,sizeof g);//把上一层状态拷贝
        
        for (int r = 0;r < v[i];r ++ )
        {
            int hh = 0,tt = -1;
            for (int j = r;j <= m;j +=v[i])
            {
                while (hh <= tt && q[hh] < j - s[i] * v[i]) hh ++ ; 
                while (hh <= tt && g[q[tt]] + (j - q[tt]) / v[i] * w[i] <= g[j] ) tt --;
                q[++tt] = j;
                f[j] = g[q[hh]] + (j - q[hh]) / v[i] * w[i]; 
            }
        }
    }
    
    cout << f[m];
    
    return 0;
}

6.混合背包问题,只需要判断一下是01背包还是完全背包

7.二维背包问题,增加一个限制条件,重量上限M。

费用增加一维,状态也增加一维即可。多循环一次

8.分组背包问题,有N件物品,背包容量为V,第i件物品最多有n[i]件可以使用,占容量c[i],价值是w[i],求装入哪些物品背包的价值最大?物品被划分为若干组,每组中物品互相冲突,最多选择一件。

三重循环,f[v] = max(f[v] , f[v - c[0] ] + w[0] , f[v - c[i] ] + w[1] , .... f[v - c[s] ] + w[s] )

9.有依赖的背包问题,选择一件物品,则必须选择他的父节点。p[i]依赖物品的编号

思路:父节点是必选的,n个子节点选或不选可以组成2^{n+1}个决策,把这些决策平铺开来,就相当于2^{n+1}个物品,然后在递归。更进一步,2^{n+1}个决策中有许多冗余,可以优化。将父节点的全部子节点看成一个物品组,对这个组先进行一次01背包,求出了占用体积为0.....V-c[i]时,这些最大价值对应为f,[0....V-c[i] ],然后把每个对应的决策看成一个物品,那么父节点和他的子节点集合相当于V - c[i] + 1个物品的物品组,其费用为c[i] + k的物品价值为f.[k] + w[i],然后使用分组背包解决。父节点还有父节点,子节点还有子节点,那么就不能把每个儿看成一个01背包,若这个子节点也有子节点,先把它转化成物品组,然后分组背包求父节点及子节点对应的价值。树形DP:每个父节点都要对他的儿子属性进行一次DP求得相关属性,“泛化”每一个子树都等价于一件泛化物品,求其节点为根的子树对应的泛化物品相当于求其所有儿子的泛化物品之和。

10.背包问题求方案数。

求最优值,最优方案,记录每个状态的最优值是由状态转移方程的哪一项推出来的,记录他是由哪个策略推出来的,便可以根据这个策略找到上一个状态,接着向前推即可。

11.这些问题很好想出来,但是能写出来代码又是另一回事。有时候能把问题总结归纳起来,写出来代码也会更迅速,不过可能更高的境界就是抛弃忘记这些归纳吧。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想念你的程客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值