• 背包问题


    -------------------  0-1背包  ------------------- 

    问题描述:

    N件物品和容量为V的背包;每种物品均只有一件,且第i件物品重量为weight[i],价值为value[i]。求将哪些物品放入背包可使物品重量总和不超过背包容量,且价值总和达到最大?

    解题思路:

    首先定义问题状态,DP[i][V]表示前i件物品放入容量为V的背包所能获得的最大值(以下分析都如此)。

    若只考虑第i件物品的放入策略,即放入或不放入两种情况,问题就可转化为一个只涉及前i-1件物品的问题,得状态转移方程:

    DP[i][v] = max{ DP[i-1][v],  DP[i-1][v-weight[i]] + value[i] }

    空间复杂度优化:

    用一维数组存储DP值,用DP[0,1,...V]表示,DP[v]表示把前i件物品放入容量为v的背包所得到的最大价值,这样可将空间复杂度从O(VN)降低到O(V)。

    那么就要解决从二维转成一维过程是否合理的问题:怎样才能在DP[v]表示当前状态(即前i件物品放入容量为v的背包中所得价值)的同时,使得DP[v]和DP[v-weight[i]]分别都能标记前一状态(即前i-1件物品分别放入容量为v和v-weight[i]的背包所得价值)?

    解决方法:时间复杂度O(VN)不变,仍为两重for循环,从下图可以看出,当外层为i循环,内层为v循环时,DP[0,1,...V]由于不断覆盖旧值的原因,将始终保存的是蓝色区域下边缘的系列值,对比观察改进前后的两个状态转移方程,可知此处DP[v]的值应采用逆序保存。

    伪代码如下:

    for i = 1,2...N
        for v = V...1,0
            DP[v] = max{DP[v], DP[v-weight[i]] + value[i]};

     

     

    -------------------  完全背包  -------------------

    问题描述:

    N件物品和容量为V的背包;每件物品无限可用,且第i件物品重量为weight[i],价值为value[i]。求将哪些物品放入背包可使物品重量总和不超过背包容量,且价值总和达到最大?

    解题思路:

    按照分析01背包思路,同样根据每件物品的放入策略,有很多种情况(不是无限种,有 0 <= k*value[i] <= v 约束条件),可得状态转移方程:

    DP[i][v] = max{ DP[i-1][v],  DP[i-1][v-k*weight[i]] + k*value[i] }

    空间复杂度优化:

    同0-1背包优化方法,DP的值仍采用一维存储,唯一区别在于DP[v]值改为顺序保存。

    这就产生了疑问:完全背包中物品的放入策略不再是两种,而是多种情况,当DP用二维存储时,程序为三重for循环,那么优化后采用一维存储为什么没有了关于k的循环?

    原因分析:在这里引用背包系列之完全背包中相关解释:

    首先想想为什么P01中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。

    由此可见,二维存储转成一维存储之前,先要在思维上过渡:

    DP[i][v] = max{DP[i-1][v], DP[i-1][v-k*weight[i]] + k*value[i]}
    
                                ||
                                 /
                                /
       DP[i][v] = max{DP[i-1][v], DP[i][v-weight[i]] + value[i]}
    
                                ||
                                 /
                                /
           DP[v] = max{DP[v], DP[v-weight[i]] + value[i]};

    伪代码如下:

    for i = 1,2...N
        for v = 0,1...V
            DP[v] = max{DP[v], DP[v-weight[i]] + value[i]};

    -------------------  多重背包  -------------------

    问题描述:

    N件物品和容量为V的背包;第i件最多有num[i]件可用,且第i件物品重量为weight[i],价值为value[i]。求将哪些物品放入背包可使物品重量总和不超过背包容量,且价值总和达到最大?

    代码如下:

    void ZeroOnePack(int v, int w)
    {
        for(int j = V; j >= w; j--)  
            DP[j] = max(DP[j], DP[j-w]+v);
    }
    void CompletePack(int v, int w)
    {
        for(int j = w; j <= V; j++)   
            DP[j] = max(DP[j], DP[j-w]+v);
    }
    void MultiplePack(int v, int w, int n)
    {
        if(w*n >= V)
        {
            CompletePack(v, w);      // 纯粹的完全背包
            return ;    
        } 
        int k = 1;
        while(k < n)
        {
            ZeroOnePack(k*v, k*w);   // 二进制思想分解
            n = n - k;
            k = k * 2;
        }
        ZeroOnePack(n*v, n*w);
    }
    /************************************************/
    // 外层的物品种类循环在main函数中 for(i = 1; i <= N; i++) { // 内层的容量循环在MultiplePack中讨论处理 MultiplePack(v[i], w[i], num[i]); }
  • 相关阅读:
    26个Jquery使用小技巧(jQuery tips, tricks & solutions)
    JavaScript中Eval()函数的作用
    基于邮件通道的WCF通信系统
    同步一个数据库要发多少个数据包?
    还在写SQL的同志,去喝杯咖啡吧!
    隐藏在程序旮旯中的“安全问题”
    在SQLMAP中使用动态SQL
    SQLSERVER 占了500多M内存,原来的程序无法一次查询出50多W数据了,记录下这个问题的解决过程。
    实例探究字符编码:unicode,utf8,default,gb2312 的区别
    Why to do,What to do,Where to do 与 Lambda表达式!
  • 原文地址:https://www.cnblogs.com/1203ljh/p/4624652.html
Copyright © 2020-2023  润新知