• 背包问题汇总(01背包、完全背包、多重背包、分组背包)


    01 背包

    有 n 件物品,每个物品只能使用一次,在不超过背包体积的情况下,总价值最大是多少?

    #include 
    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++) // 选第 i 个物品 
        {
            for(int j = 0; j <= m; j++) // 枚举体积
            {
                f[i][j] = f[i - 1][j];
                if(j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
            }
        }
        cout << f[n][m] << endl;
        return 0;
    }
    

    状态表示可以优化为一维数组!

    f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]); 这里求 f[i][j] 使用的是 f[i-1] 层的东西,所以在优化为一维后,需要从后往前遍历 j ,这样可以确保后面使用的 f[i - 1] 是上一层未被更新的,而不是更新后的!

    优化后:

    #include 
    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++) // 选第 i 个物品 
        {
            for(int j = m; j >= v[i]; j--) // 枚举体积
            {
                f[j] = max(f[j], f[j - v[i]] + w[i]);
            }
        }
        cout << f[m] << endl;
        return 0;
    }
    

    完全背包

    每个物品可以拿无数个,求不超过背包体积的情况下,总价值最大是多少

    暴力写法:可以直接采用 拿与不拿 的思想,第 i 个物品最多可以拿 k 个,在枚举物品种类和价值的基础下,再枚举这 k 种情况即可

    #include 
    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 = 0; j <= m; j++)
                for(int k = 0; k* v[i] <= j; k++)
                    f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
        cout << f[n][m];
        return 0;
    }
    

    使用数学方法经化简可以得出:f[i][j]最大值恰好比 f[i][j - v[i]] 多了 w[i],那么 f[i][j]状态转移方程就可以优化为:f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i]);,这样,就直接可以使用一维背包的套路来做了:

    #include 
    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 = 0; 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];
        return 0;
    }
    

    优化为一维:但完全背包的 j 是从 v[i] 开始,往大的枚举,这个与 01 背包恰好相反,其他的一维优化后与 01 背包别无二致!

    #include 
    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];
        return 0;
    }
    

    多重背包

    有 N 种物品和一个容量是 V 的背包。第 i种物品最多有 s[i] 件,每件体积是 v[i],价值是 w[i],求不超过背包容量的情况下,总价值最大时多少?

    暴力解法,在枚举物品种类和体积的基础上,依次枚举符合符合条件的物品个数(k <= s[i] && k * v[i] < j)

    时间复杂度:O(n* m * s)

    #include 
    using namespace std;
    const int N = 110;
    int n, m;
    int v[N], w[N], s[N];
    int f[N][N];
    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++)
            for(int j = 0; j <= m; j++)
                for(int k = 0; k <= s[i] && k * v[i] <= j; k++)
                    f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
        cout << f[n][m] << endl;
        return 0;
    }
    

    优化:二进制优化

    每个物品可选择的个数 s[i],都能通过 多个2^x + 某一个数 来表示出来,比如,76,就可以用 1 + 2 + 4 + 8 + 16 + 32 + 13 来表示,那么枚举 s[i] 个第 i 个体积为 w[i] 的数,就转化为了枚举:
    一个体积为 v[i],价值为w[i]的物品, 一个体积为 2 * v[i],价值为2 * w[i]的物品,一个体积为 4 * v[i],价值为4 * w[i]的物品 …,一个体积为 k * v[i],价值为 w[i] 的物品。这样,时间复杂度就被优化为了 :O(n* m* logs)

    最后就转化为了:cnt 个物品的 01 背包问题!

    #include 
    using namespace std;
    // s[i] 2000 可以拆分为 log2000 ~= 11 个物品, 所以开 12000 个空间足够 
    const int N = 12000;
    int n, m;
    int v[N], w[N];
    int f[N];
    int main()
    {
        cin >> n >> m;
        int cnt = 0; // 记录优化后的物品编号
        for(int i = 1; i <= n; i++)
        {
            int a, b, s;
            cin >> a >> b >> s;
            int k = 1;
            while(k <= s)
            {
                cnt ++;
                v[cnt] = k * a;
                w[cnt] = k * b;
                s -= k;
                k *= 2;
            }
            if(s > 0) // 剩余的不能使用 2^ x 次方表示的
            {
                cnt ++;
                v[cnt] = s * a;
                w[cnt] = s * b;
            }
        }
        n = cnt;
        for(int i = 1; i <= n; i++)
            for(int j = m; j >= v[i]; j--)
                f[j] = max(f[j], f[j - v[i]] + w[i]);
        cout << f[m] << endl;
        return 0;
    }
    

    分组背包

    有 N 组物品和一个容量是 V 的背包,每组物品有若干个,同组内的物品最多只能选一个。

    利用 dp 中选或不选的思路,枚举 k + 1 种情况:不选第 i 种物品;选第一个;选第二个;选第三个…

    三重循环枚举,时间复杂度:O(N^3)

    代码易错点:ks[] 是从 0 开始还是从1开始必须想好,更新f[i][j] = f[i - 1][j];,必须放在第三重循环外,第二重循环内!

    #include 
    using namespace std;
    const int N = 110;
    int v[N][N], w[N][N], s[N];
    int m, n;
    int f[N][N];
    int main()
    {
        cin >> n >> m;
        for(int i = 1; i <= n; i++)
        {
            cin >> s[i]; // 输入第 i 种物品的个数
            for(int j = 1; j <= s[i]; j++)
                cin >> v[i][j] >> w[i][j]; // 输入第 i 种物品的 k 个体积和价值 
        }
        for(int i = 1; i <= n; i++) // 枚举物品种类 
        {
            for(int j = 0; j <= m; j++) // 枚举体积
            {
                f[i][j] = f[i - 1][j]; // 必须放外面! 因为这是不选的情况 
                for(int k = 1; k <= s[i]; k++) // 枚举第 i 个数的 k 种取法
                    if(j >= v[i][k]) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
            }
        }
        cout << f[n][m] << endl;
        return 0;
    }
    

    优化为一维:

    #include 
    using namespace std;
    const int N = 110;
    int v[N][N], w[N][N], s[N];
    int m, n;
    int f[N];
    int main()
    {
        cin >> n >> m;
        for(int i = 1; i <= n; i++)
        {
            cin >> s[i]; // 输入第 i 种物品的个数
            for(int j = 1; j <= s[i]; j++)
                cin >> v[i][j] >> w[i][j]; // 输入第 i 种物品的 k 个体积和价值 
        }
        for(int i = 1; i <= n; i++) // 枚举物品种类 
        {
            for(int j = m; j >= 0; j--) // 因为要用上一层的状态,因此必须从大到小 
            {
                for(int k = 1; k <= s[i]; k++) // 枚举第 i 个数的 k 种取法
                    if(j >= v[i][k])  // 说明可以选 
                        f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
            }
        }
        cout << f[m] << endl;
        return 0;
    }
    
  • 相关阅读:
    XXE漏洞中DOCTYPE、ENTITY傻傻分不清-WEB安全基础入门—XML外部实体注入(XXE)
    【MySQL】复合查询
    python游戏库pygame经典教程
    网安学习-Windows权限提升1
    An English-Chinese Chinese-English Glossary of Computer Science and Technology
    数据结构 - 树 堆
    分享几个查看操作系统版本信息的方法
    Flutter全局menu弹框
    UI自动化之混合框架
    Linux常用指令和选项(一)
  • 原文地址:https://blog.csdn.net/zyb___/article/details/139299201