前言
背包问题大致分为四种:01背包、完全背包、多重背包和分组背包
01背包
有 个物品,每个物品的体积是 ,价值是 ,拿⾛体积之和不超过 的物品,最多能拿到多少价值?
做法:
定义 :前 个物品,占⽤了不超过 个单位的体积。
转移⽅程:
复杂度 即可解决。可以使⽤滚动数组优化空间,注意内层循环要从⼤到⼩。
模版例题:P1048 [NOIP2005 普及组] 采药
参考代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1005;
int dp[N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int V, n;
cin >> V >> n;
for(int i = 1; i <= n; i++)
{
int v, w;
cin >> v >> w;
for (int j = V; j >= v; j--)
dp[j] = max(dp[j], dp[j - v] + w);
}
cout << dp[V];
return 0;
}
上面的代码是运用滚动数组来实现的背包问题,下面给大家介绍一下滚动数组的用法与定义。
滚动数组
在整个过程,可以发现每次内层循环运算时所需要的参数只有第 行,所以只需要将数组改为一维数组,是没有问题的,因为同样都有 和,可以记录两个参数。也不许数组和数组,只需要改为变量即可。(要注意滚动数组只能优化空间,不能优化时间,如果要使用滚动数组的话,内层循环需改为从大到小,否则就会变成完全背包,即物品数量无限个)
拓展
其实,还有一种变形的01背包——依赖性01背包
题目链接:P1064 [NOIP2006 提高组] 金明的预算方案
参考代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int dp[32005];
int v[65], w[65], c[65];
vector<int> g[65];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int V, n;
cin >> V >> n;
for (int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i] >> c[i];
w[i] *= v[i];
g[c[i]].emplace_back(i);
}
for(int i = 1; i <= n; i++)
{
if(!c[i])
{
for (int j = V; j >= v[i]; j--)
{
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
if (g[i].size() >= 1 && j >= v[i] + v[g[i][0]])
dp[j] = max(dp[j], dp[j - v[i] - v[g[i][0]]] + w[i] + w[g[i][0]]);
if (g[i].size() >= 2 && j >= v[i] + v[g[i][1]])
dp[j] = max(dp[j], dp[j - v[i] - v[g[i][1]]] + w[i] + w[g[i][1]]);
if (g[i].size() >= 2 && j >= v[i] + v[g[i][0]] + v[g[i][1]])
dp[j] = max(dp[j], dp[j - v[i] - v[g[i][0]] - v[g[i][1]]] + w[i] + w[g[i][0]] + w[g[i][1]]);
}
}
}
cout << dp[V];
return 0;
}
解析
emplace_back() 此函数的功能与 push_back() 的功能一样,但速度不同,前者快,后者慢。
(注意:emplace_back() 因为 vector 的工作原理在特殊情况下会报错,请慎用!报错了就换push_back() )
还记得01背包的决策是什么吗?
1.不选,然后去考虑下一个
2.选,背包容量减掉那个重量,总值加上那个价值。
这个题的决策是五个,分别是:
1.不选,然后去考虑下一个
2.选且只选这个主件
3.选这个主件,并且选附件1
4.选这个主件,并且选附件2
5.选这个主件,并且选附件1和附件2.
转移方程:
if (g[i].size() >= 1 && j >= v[i] + v[g[i][0]])
dp[j] = max(dp[j], dp[j - v[i] - v[g[i][0]]] + w[i] + w[g[i][0]]);
if (g[i].size() >= 2 && j >= v[i] + v[g[i][1]])
dp[j] = max(dp[j], dp[j - v[i] - v[g[i][1]]] + w[i] + w[g[i][1]]);
if (g[i].size() >= 2 && j >= v[i] + v[g[i][0]] + v[g[i][1]])
dp[j] = max(dp[j], dp[j - v[i] - v[g[i][0]] - v[g[i][1]]] + w[i] + w[g[i][0]] + w[g[i][1]]);
完全背包
有 种物品,每种物品有⽆限个,第 种物品的每个物品体积是 ,价值是 ,拿⾛体积之和不超过 的物品,最多能拿到多少价值。
做法:
转移方程:
模版例题:P1616 疯狂的采药
参考代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[10000005];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int V, n;
cin >> V >> n;
for(int i = 1; i <= n; i++)
{
int v, w;
cin >> v >> w;
for (int j = v; j <= V; j++)
dp[j] = max(dp[j], dp[j - v] + w);
}
cout << dp[V];
return 0;
}
多重背包
有 种物品,每种物品有 个,第 种物品的每个物品体积是 ,价值是 ,拿⾛体积之和不超过 的物品,最多能拿到多少价值。
做法1:
转化为01背包。将每种物品分成组,第 组的个数是,这样任意数量的物品都可以组合出来。复杂度是.
状态转移方程:
与01背包一致。
做法2:
使用单调队列优化(单调队列在此出不做介绍)
模版例题:宝物筛选
参考代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 40005;
int dp[N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n, V;
cin >> n >> V;
while(n--)
{
int w, v, c;
cin >> w >> v >> c;
for (int i = 1; c; i *= 2)
{
if(i <= c)
c -= i;
else
i = c, c = 0;
for (int j = V; j >= v * i; j--)
dp[j] = max(dp[j], dp[j - v * i] + w * i);
}
}
cout << dp[V];
return 0;
}
分组背包
有 个物品,每个物品的体积是 ,价值是 。 个物品被分为若⼲组,每组最多只能拿⾛⼀ 个物品。拿⾛体积之和不超过 的物品,最多能拿到多少价值。
做法:
模版例题:通天之分组背包
参考代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1005;
vector <int> v[N];
int dp[N], a[N], b[N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int V, n;
cin >> V >> n;
for(int i = 1; i <= n; i++)
{
int z;
cin >> a[i] >> b[i] >> z;
v[z].emplace_back(i);
}
for(int i = 1; i <= n; i++)
{
for(int j = V; j >= 0; j--)
{
for (vector<int>::iterator k = v[i].begin(); k != v[i].end(); k++)
{
if(j >= a[*k])
dp[j] = max(dp[j], dp[j - a[*k]] + b[*k]);
}
}
}
cout << dp[V];
return 0;
}
后续练习内容,已经放置于主页,并绑定于此文章。喜欢的点个关注+收藏+赞!