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

背包问题

01 背包

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

cpp 复制代码
#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++) // 选第 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] 是上一层未被更新的,而不是更新后的!

优化后:

cpp 复制代码
#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++) // 选第 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 种情况即可

cpp 复制代码
#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 = 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]);,这样,就直接可以使用一维背包的套路来做了:

cpp 复制代码
#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 = 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 背包别无二致!

cpp 复制代码
#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];
    return 0;
}

多重背包

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

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

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

cpp 复制代码
#include <iostream>
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 背包问题!

cpp 复制代码
#include <iostream>
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];,必须放在第三重循环外,第二重循环内!

cpp 复制代码
#include <iostream>
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;
}

优化为一维:

cpp 复制代码
#include <iostream>
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;
}
相关推荐
不想当程序猿_8 分钟前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
落魄君子19 分钟前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
冷眼看人间恩怨24 分钟前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
菜鸡中的奋斗鸡→挣扎鸡27 分钟前
滑动窗口 + 算法复习
数据结构·算法
红龙创客34 分钟前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin36 分钟前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
郭wes代码43 分钟前
Cmd命令大全(万字详细版)
python·算法·小程序
scan7241 小时前
LILAC采样算法
人工智能·算法·机器学习
菌菌的快乐生活1 小时前
理解支持向量机
算法·机器学习·支持向量机
大山同学1 小时前
第三章线性判别函数(二)
线性代数·算法·机器学习