多重背包、分组背包、混合背包和多维背包
一、多重背包
1、介绍
物品个数是多个,但不是无穷个。
例题:多重背包
2、分析
dp[i][j]表示在[1, i]中选,重量不超过j的最大价值
不选i:dp[i][j] = dp[i - 1][j];
选i:由于物品个数不是无限的,所以不能优化只能一个一个写
选一个:dp[i][j] = dp[i - 1][j - w[i]] + v[i];
选二个:dp[i][j] = dp[i - 1][j - 2 * w[i]] + 2 * v[i];
.....
选最多x[i]个:dp[i][j] = dp[i - 1][j - x[i] * w[i]] + x[i] * v[i];
当然枚举过程中j - k * w[i] >= 0
初始化返回值都和之前问题一样
3、代码
#include <bits/stdc++.h>
using namespace std;
int w[110];
int v[110];
int x[110];
int n, t;
int dp[110][110];
int main()
{
cin >> n >> t;
for(int i = 1; i <= n; i++)
cin >> x[i] >> w[i] >> v[i];
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= t; j++)
{
for(int k = 0; k <= x[i] && j - k * w[i] >= 0; k++)
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * v[i]);
}
}
cout << dp[n][t];
return 0;
}
4、空间优化
只用到了左上方的数据,所以从右向左
#include <bits/stdc++.h>
using namespace std;
int w[110];
int v[110];
int x[110];
int n, t;
int dp[110];
int main()
{
cin >> n >> t;
for(int i = 1; i <= n; i++)
cin >> x[i] >> w[i] >> v[i];
for(int i = 1; i <= n; i++)
{
for(int j = t; j >= 0; j--)
{
for(int k = 0; k <= x[i] && j - k * w[i] >= 0; k++)
dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]);
}
}
cout << dp[t];
return 0;
}
5、二进制优化
二进制优化:优化后最终结果会把多重背包变成01背包
其实多重背包不做任何优化也能看作01背包,因为个数有限,可以把每一个物品拆出来
基于这种思想不难想到可以把同一种物品的10进制个数通过二进制表示出来,这样优化的是logX
假设一个物品个数是9,重量w,价值v
9 = 1 + 2 + 4 + 2,即可以看成有4种物品分别的重量和价值是[w, v], [2w, 2v], [4w, 4v], [2w, 2v]
然后进行01背包处理
这样从O(n * t * x)->O(n * t * logx)
没有数量数组,原数组由于优化,每种物品分成了logX种物品,所以大小还要乘logX
求方案数不能二进制优化,因为同一种物品被我们分成了logx种物品
即优化后的不同数据不能代表不同的物品,也就不能代表一种方案
int w[5 * 110];
int v[5 * 110];
int n, t;
int sz; // 新的个数用sz记录
int dp[110];
int main()
{
cin >> n >> t;
for(int i = 1; i <= n; i++)
{
int x, y, z;
cin >> x >> y >> z;
// 同一物品开始二进制分类
int p = 1;
while(x >= p)
{
sz++;
x -= p;
w[sz] = y * p;
v[sz] = z * p;
p *= 2;
}
if(x)
{
sz++;
w[sz] = y * x;
v[sz] = z * x;
}
}
for(int i = 1; i <= sz; i++)
{
for(int j = t; j >= w[i]; j--)
{
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[t];
return 0;
}
二、分组背包
1、介绍
在每一组中挑一个物品
例题:分组背包
2、分析
dp[i][j]表示在小组[1, i]中选,每组选一个物品,重量不超过j的最大价值
不选i: dp[i][j] = dp[i - 1][j];
选i: 选i组的第一个: dp[i][j] = dp[i - 1][j - w[i][0]] + v[i][0];
选i: 选i组的第二个: dp[i][j] = dp[i - 1][j - w[i][1]] + v[i][1];
.....
选i: 选i组的第x个: dp[i][j] = dp[i - 1][j - w[i][x - 1]] + v[i][x - 1];
3、代码
// https://www.luogu.com.cn/problem/P1757
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
int n, m;
vector<pii> g[110];
int k = 0;
int dp[110][1010];
int main()
{
cin >> m >> n;
for(int i = 1; i <= n; i++)
{
int W, V, K;
cin >> W >> V >> K;
g[K].push_back({W, V});
k = max(k, K);
}
for(int i = 1; i <= k; i++)
{
for(int j = m; j >= 0; j--)
{
dp[i][j] = dp[i - 1][j];
for(auto& [a, b] : g[i])
{
if(j - a >= 0)
dp[i][j] = max(dp[i][j], dp[i - 1][j - a] + b);
}
}
}
cout << dp[k][m];
return 0;
}
4、空间优化
空间优化:用的是左上,所以从右向左
int dp[1010];
int main()
{
cin >> m >> n;
for(int i = 1; i <= n; i++)
{
int W, V, K;
cin >> W >> V >> K;
g[K].push_back({W, V});
k = max(k, K);
}
for(int i = 1; i <= k; i++)
{
for(int j = m; j >= 0; j--)
{
for(auto& [a, b] : g[i])
{
if(j - a >= 0)
dp[j] = max(dp[j], dp[j - a] + b);
}
}
}
cout << dp[m];
return 0;
}
三、混合背包
1、介绍
01背包,完全背包,多重背包,分组背包结合在一起的背包问题
例题:樱花
2、分析
就是遇到一种物品属于什么背包问题就用对应的状态方程解决即可。
3、代码
int m, n;
int t[10010];
int c[10010];
int p[10010];
int dp[10010][1010];
int main()
{
int h1, m1, h2, m2;
char ch;
cin >> h1 >> ch >> m1 >> h2 >> ch >> m2 >> n;
m = h2 * 60 + m2 - h1 * 60 - m1;
for(int i = 1; i <= n; i++)
cin >> t[i] >> c[i] >> p[i];
for(int i = 1; i <= n; i++)
{
if(p[i] == 0)
{
for(int j = 0; j <= m; j++)
{
dp[i][j] = dp[i - 1][j];
if(j - t[i] >= 0)
dp[i][j] = max(dp[i][j], dp[i][j - t[i]] + c[i]);
}
}
else
{
for(int j = m; j >= 0; j--)
{
dp[i][j] = dp[i - 1][j];
for(int k = 1; k <= p[i] && j - k * t[i] >= 0; k++)
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * t[i]] + k * c[i]);
}
}
}
cout << dp[n][m];
return 0;
}
4、空间优化
注意完全背包是从左向右即可
int dp[1010];
int main()
{
int h1, m1, h2, m2;
char ch;
cin >> h1 >> ch >> m1 >> h2 >> ch >> m2 >> n;
m = h2 * 60 + m2 - h1 * 60 - m1;
for(int i = 1; i <= n; i++)
cin >> t[i] >> c[i] >> p[i];
for(int i = 1; i <= n; i++)
{
if(p[i] == 0)
{
for(int j = t[i]; j <= m; j++)
{
dp[j] = max(dp[j], dp[j - t[i]] + c[i]);
}
}
else
{
for(int j = m; j >= t[i]; j--)
{
for(int k = 1; k <= p[i] && j - k * t[i] >= 0; k++)
dp[j] = max(dp[j], dp[j - k * t[i]] + k * c[i]);
}
}
}
cout << dp[m];
return 0;
}
四、多维背包
1、介绍
限制条件增多的背包问题,必须要空间优化。
例题:L 国的战斗之间谍
2、分析
dp[i][j][k]表示在[1, i]中选,暴露不超过j,工资不超过j的最大价值
不选i: dp[i][j][j] = dp[i - 1][j][k];
选i: dp[i][j][k] = dp[i - 1][j - b[i]][k - c[i]] + a[i];
3、空间优化代码
从右向左
// https://www.luogu.com.cn/problem/P1910
// 多维背包:限制条件变多,那就加维数
#include <bits/stdc++.h>
using namespace std;
int n, m, x;
int a[110];
int b[110];
int c[110];
int dp[1010][1010];
// dp[i][j][k]表示在[1, i]中选,暴露不超过j,工资不超过j的最大价值
// 不选i: dp[i][j][j] = dp[i - 1][j][k];
// 选i: dp[i][j][k] = dp[i - 1][j - b[i]][k - c[i]] + a[i];
// 必须空间优化
int main()
{
cin >> n >> m >> x;
for(int i = 1; i <= n; i++)
cin >> a[i] >> b[i] >> c[i];
for(int i = 1; i <= n; i++)
{
for(int j = m; j >= b[i]; j--)
{
for(int k = x; k >= c[i]; k--)
{
dp[j][k] = max(dp[j][k], dp[j - b[i]][k - c[i]] + a[i]);
}
}
}
cout << dp[m][x];
return 0;
}
五、例题
1、摆花
#include <bits/stdc++.h>
using namespace std;
int n, m;
int a[110];
const int mod = 1e6 + 7;
// dp[i][j]表示在[1, i]种物品种选,个数刚好j的总方案数
// 不选i: dp[i][j] = dp[i - 1][j];
// 选一个i: dp[i][j] += dp[i - 1][j - 1];
// 选二个i: dp[i][j] += dp[i - 1][j - 2];
// ....
// 选a[i]个i: dp[i][j] += dp[i - 1][j - a[i]];
// j - x >= 0;
// 初始化:dp[0][0] = 1; dp[0][j] = 0
int main()
{
cin >> n >> m;
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
dp[0][0] = 1;
for(int i = 1; i <= n; i++)
cin >> a[i];
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= m; j++)
{
for(int k = 0; k <= a[i] && j - k >= 0; k++)
dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % mod;
}
}
cout << dp[n][m];
return 0;
}
2、排兵布阵
#include <bits/stdc++.h>
using namespace std;
int s, n, m;
int g[110][110];
int dp[110][20010];
// https://www.luogu.com.cn/problem/P5322
// 对于每一个人的每一个城堡我们都能*2+1找到最少占领这个城堡的分配士兵数
// 即问题转化成了在每一组城堡中我挑出一个数,计算能占领几个这样的城堡
// dp[i][j]表示在[1, i]的城堡中,派出不超过m名士兵后的最大得分
// 存储数组g[110][110],一维城堡编号,二维玩家编号,每一行排好序
// 不派:dp[i][j] = dp[i - 1][j];
// 派g[i][p]的士兵争夺城堡i: dp[i][j] = dp[i - 1][j - g[i][p]] + i * p;
int main()
{
cin >> s >> n >> m;
for(int i = 1; i <= s; i++)
{
for(int j = 1; j <= n; j++)
{
cin >> g[j][i];
g[j][i] = 2 * g[j][i] + 1;
}
}
for(int i = 1; i <= n; i++)
sort(g[i] + 1, g[i] + s + 1);
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= m; j++)
{
dp[i][j] = dp[i - 1][j];
for(int p = 1; p <= s; p++)
{
if(j - g[i][p] >= 0)
dp[i][j] = max(dp[i][j], dp[i - 1][j - g[i][p]] + p * i);
}
}
}
cout << dp[n][m];
return 0;
}更多推荐



所有评论(0)