背包问题
一、01背包问题
思路
f[i][j] 表示只看前1个物品,总体积是j的情况下,总价值最大是多少
不选第i个物品 f[i][j] = f[i-1][j];
选第i个物品f[i][j] = f[i - 1][j - v[i]];
f[i][j] = max{1. 2};
f[0][0] = 0;
优化
省略第一维度
迭代体积从m开始从大体积到小体积进行迭代。因为第i轮用的是第i-1轮的数据,若从小体积到大体积进行迭代,则计算大体积时用到的可能是第i轮更新后的体积。
代码
#include <iostream>
#include <algorithm>
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 = m; j >= v[i]; j -- )
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
二、完全背包问题
代码速记
将01代码的循环倒过来
思路
Dp
- 状态表示 f(i, j)
- 集合:只从前i个物品中选,总体积不超过j的方案的集合
- 属性
- 状态计算
- 选0个第i个物品、1个第i个物品…k个
递推公式
1. 01背包: f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w);
2. 完全背包: f[i][j] = max(f[i - 1][j], f[i][j - v] + w);
代码
#include <iostream>
#include <algorithm>
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]); //只有j循环的顺序和01背包不一样
cout << f[m] << endl;
return 0;
}
三、多重背包问题Ⅰ
概述
添加限制调价:第i种物品最多有 s i s_i si件
思路
在01背包基础上加一重循环,01背包是只能选一件。
多重背包是选s+1次
代码
#include <iostream>
#include <algorithm>
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 - v[i] * k] + w[i] * k);
cout << f[n][m] << endl;
四、多重背包问题Ⅱ
概述
和三题目一样,但需要用二进制优化,数据量
思路
l o g 2 log_2 log2 + 拆物品
0~7 可由三位二进制表示
对于10 拆成 0 + 2 + 4 + 4
代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[M];
vector<PII> goods;
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i++)
{
int v, w, s;
cin >> v >> w >> s;
for(int k = 1; k <= s; k *= 2)
{
s -= k;
goods.push_back({v * k, w * k});
}
if(s > 0) goods.push_back({s * v, s * w});
}
for(int i = 0; i < goods.size(); i++)
{
auto t = goods[i];
int v = t.first, w = t.second;
for(int j = m; j >= v; j --)
f[j] = max(f[j], f[j - v] + w);
}
cout << f[m] << endl;
return 0;
}
五、分组背包问题
概述
每组物品有若干个,同一组内的物品最多只能选一个
思路
三重循环//比较笨,循环体积从m开始
代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 110;
int n, m;
int f[N], v[N], w[N];
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++)
{
int s;
cin >> s;
for(int j = 0; j < s; j++) cin >> v[j] >> w[j];
for(int j = m; j >= 0; j--)
for(int k = 0; k < s; k++)
{
if(j >= v[k]) f[j] = max(f[j], f[j - v[k]] + w[k]);
}
}
cout << f[m] << endl;
return 0;
}