• AcWing 3. 完全背包问题


    题目传送门

    一、完全背包模板

    (01)背包的代码,容量循环的顺序变成由小到大即可。

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1010;
    int n, m;
    int v[N], w[N];
    int f[N];
    
    
    //完全背包问题
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    
        for (int i = 1; i <= n; i ++ )
            for (int j = v[i]; j <= m; j ++ ) //一个一个加上来,求一个最大值
                f[j] = max(f[j], f[j - v[i]] + w[i]); 
        cout << f[m] << endl;
        return 0;
    }
    

    二、01背包与完全背包的本质差别

    状态表示:(f(i,j))
    集合: (i)是指在前(i)个物品中进行选择,(j)是指在总体积不超过(j)的容量下进行选择。
    属性: 求一个最大值。 因为按上面的集合进行选择,每选择一次,就会出现一个结果,我们想求的是这些结果中的最大值。

    状态计算
    (01)背包不一样,(01)背包是每一个物品选与不选,完全背包不是这个意思。是可以不选,选择(1)个,或者选择多个。

    就是按选择(0-k)个物品,划分成了(k+1)个集合,原来两个选与不选,到这里就变成了(k+1)种方案,我们需要逐个讨论才能完整描述事实。

    (f(i,j)=max(f(i-1,j),f(i-1,j-v_i)+w_i,f(i-1,j-2*v_i)+2w_i,....)

    上面的公式简单理解一下,就是第(i)个物品
    (1) 可以不取:(f(i-1,j))
    表示还在前(i-1)个物品中选择,第(i)个不参加选择,由于没有选择,所以空间还剩余(j)这么大。

    (2) 选择1个:(f(i-1,j-v_i)+w_i)
    表示选择了一个第(i)个物品,其它的都需要从前(i-1)个物品中选择,由于使用了一个(i)物品,所以袋子的容量减少(v_i),剩余 (j-v_i),同时,价值增加了(w_i).

    (3) 选择(2)(3)(4)...都与选择(1)是一样的,一直到选择(k+1)(i)号物品,使得体积大于了剩余容量,就不能继续了。

    至此,我们已经得到了状态转移方程,是可以直接用来求解,方法就是三重循环,伪代码如下:

    (for(i=1;i<=n;i++)) 物品
    (qquad) (for(j=1;j<=m;j++)) 体积
    (qquad) (qquad) 在前(i)件物品的前提下,在(j)限定的空间里,遍历所有可有存在的个数
    (qquad) (qquad) (qquad) 求出最大值

    这样做,需要三重循环,本题给出的(n,m)的取值范围是1000,三重循环的时间复杂度就是(10^3 * 10^3 * 10^3=10^9),而(c++)的一秒能计算(10^7)(10^8),所以,会超时,需要继续优化。

    那么如何进行优化呢?数学是个好东西,现在让数学大神出场:

    已知:
    (f(i,j)=max(f(i-1,j),f(i-1,j-v_i)+w_i,f(i-1,j-2 imes v_i)+2w_i,....)

    如果(j=j-v_i)时,代入上式:

    (f(i,j-v_i)=max(f(i-1,j-v_i),f(i-1,j-2 imes v_i)+w_i,f(i-1,j-3 imes v_i)+2w_i,....)

    新产生的式子,和第一个式子是很像的,但每项缺少一个(w_i),将新产生的式子代入最上面的式子:就是
    $f(i,j-v_i)+w_i=max(f(i-1,j-v_i)+w_i,f(i-1,j-2 imes v_i)+2 * w_i,f(i-1,j-3 imes v_i)+3 * w_i,.... $

    (f(i,j)=max(f(i-1,j),f(i,j-v_i)+w_i))

    太神奇了!!一般同学们学习完全背包问题时,都喜欢直接看状态转移方程来理解含义,结果发现理解不上去。为什么会发生这样的事情呢?原因很简单,因为人家根本不是从现实直接过度到方程的,人家中间是有一顿推导过程的,你略过了中间过程,肯定看结果看不懂了,啥叫循序渐进?啥叫学个通透?要知其然,并知其所以然才能学的精通。

    总结一下
    二维情况下的状态转移方程

    0 $ $1背包 : (f[i][j]=max(f[i-1][j],f[i-1][j-v_i]+w_i))
    完全背包: (f[i][j]=max(f[i-1][j],f[i][j-v_i]+w_i))

    这样,再用二维除一维的思路来思考,就得到了完全背包的终极解法:

     for (int i = 1; i <= n; i ++ )
            for (int j = v[i]; j <= m; j ++ ) 
                f[j] = max(f[j], f[j - v[i]] + w[i]); 
    

    C++ 代码【二维朴素写法】

    #include <iostream>
    using namespace std;
    const int N = 1010;
    int n, m;
    int v[N], w[N];
    int f[N][N]; 
    //枚举体积有限制是在优化后的算法有限制,在两维时候想怎么循环就怎么循环,是没有限制的
    //为什么优化后要有限制呢??
    //这是因为在二维里,有一个是不是剩余容量能装得下当前物品的判断,在优化的版本去掉了这句话,把它放到循环条件中了。
    
    //完全背包问题
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= m; j ++ ) {
                f[i][j] = f[i-1][j];
                if(j>=v[i]) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]); 
            }
        cout << f[n][m] << endl;
        return 0;
    }
    

    C++ 代码 【一维终极解法】

    #include <iostream>
    using namespace std;
    const int N = 1010;
    int n, m;
    int v[N], w[N];
    int f[N];
    
    
    //完全背包问题
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    
        for (int i = 1; i <= n; i ++ )
            for (int j = v[i]; j <= m; j ++ ) //一个一个加上来,求一个最大值
                f[j] = max(f[j], f[j - v[i]] + w[i]); 
        cout << f[m] << endl;
        return 0;
    }
    

    三、思考

    为什么01背包要倒着推,完全背包要顺着推
    https://www.cnblogs.com/MyNameIsPc/p/7782700.html

  • 相关阅读:
    os
    linux常用命令
    css-基础知识
    awk命令详解
    文献综述
    微信JSAPI支付
    SNMP详解
    SNMP进阶
    SNMP协议入门
    SNMP简单网络管理协议
  • 原文地址:https://www.cnblogs.com/littlehb/p/15370457.html
Copyright © 2020-2023  润新知