背包问题汇总
0-1背包
题目:https://www.acwing.com/problem/content/2/
分析:
d p [ i ] [ j ] 意 思 是 只 放 前 i 个 物 品 在 总 体 积 是 j 的 情 况 下 的 物 品 的 贡 献 dp[i][j]意思是只放前i个物品在总体积是j的情况下的物品的贡献 dp[i][j]意思是只放前i个物品在总体积是j的情况下的物品的贡献
易得状态方程为:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] + w [ i ] ] ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]+w[i]]) dp[i][j]=max(dp[i−1][j],dp[i−1][j−v[i]+w[i]])
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] 意 思 是 不 选 第 i 个 物 品 ( d p [ i ] [ j ] 直 接 原 封 不 动 保 留 上 一 个 i 的 状 态 ) dp[i][j]=dp[i-1][j]意思是不选第i个物品(dp[i][j]直接原封不动保留上一个i的状态) dp[i][j]=dp[i−1][j]意思是不选第i个物品(dp[i][j]直接原封不动保留上一个i的状态)
代码如下:
#include <iostream>
#include <vector>
#include <cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,m,v[1005],w[1005],dp[1005][1005];
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) {
dp[i][j] = dp[i - 1][j];
if (j >= v[i]) //要当前背包的总体积大于v[i]才能把i物品放进背包
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
}
}
// for (int i = 1; i <=n ; ++i) {
// for (int j = 0; j <=m ; ++j) {
// cout<<dp[i][j]<<' ';
// }
// cout<<endl;
// }
int res=0;
for (int i = 0; i <=m ; ++i) {
res=max(res,dp[n][i]); //dp[n]意思是将n个物品都权衡利弊后选择n个物品中的部分放入背包(不超过背包最大容量),
} //但不知道这些物品的总体积是多少,所以遍历所有可能的体积输出价值最大值即为答案。
cout<<res<<endl;
}
测试数据:
4 5
1 2
2 4
3 4
4 5
dp数组内容:
0 2 2 2 2 2
0 2 4 6 6 6
0 2 4 6 6 8
0 2 4 6 6 8
最后输出:8
优化后的代码:
只用了一维
#include <iostream>
#include <vector>
#include <cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,m,v[1005],w[1005],dp[1005];
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) { //j从大到小循环是为了让dp[j-v[i]]的值是上一层i的值
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);//(dp[j-v[i]]后于dp[j]更新)
//cout<<dp[j]<<' ';
}
//cout<<endl;
}
cout<<dp[m];
}
dp数组内容:
2 2 2 2 2
6 6 6 4
8 6 6
8 6
8
注:若要求达到最大价值时的最大体积需将初始化dp[N]={0}改为dp[0]=0,剩余dp为-INF; ans从直接输出dp[m]变为max(dp[0…m]).
状态方程的详细求解过程:
二维优化成一维的推导:
完全背包
题目:https://www.acwing.com/problem/content/3/
d p [ i ] [ j ] dp[i][j] dp[i][j]同样也是表示从前i个物品中选,总体积不超过j的方案集合,唯一的区别就是完全背包问题中每个物品都可以取无限个
所以可得到:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
,
d
p
[
i
−
1
]
[
j
−
2
∗
v
[
i
]
]
+
2
∗
w
[
i
]
,
.
.
.
)
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-2*v[i]]+2*w[i],...)
dp[i][j]=max(dp[i−1][j],dp[i−1][j−v[i]]+w[i],dp[i−1][j−2∗v[i]]+2∗w[i],...)
按道理来说需要三层循环,但是可以被优化成二层循环:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
v
[
i
]
]
+
w
[
i
]
)
dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])
dp[i][j]=max(dp[i−1][j],dp[i][j−v[i]]+w[i])
具体证明:
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,dp[1005][1005];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n,m,v[1005],w[1005];
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++){
dp[i][j]=dp[i-1][j];
if(j>=v[i])dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
}
}
cout<<dp[n][m];
}
二维优化成一维:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − v [ i ] ] + w [ i ] ) → d p [ j ] = m a x ( d p [ j ] , d p [ j − v [ i ] ] + w [ i ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])\to dp[j]=max(dp[j],dp[j-v[i]]+w[i]) dp[i][j]=max(dp[i−1][j],dp[i][j−v[i]]+w[i])→dp[j]=max(dp[j],dp[j−v[i]]+w[i])
与0-1背包不同的是,0-1背包的 d p [ j − v [ i ] ] dp[j-v[i]] dp[j−v[i]]对应的是i上一层也就是i-1层的值,而这里的 d p [ i ] [ j − v [ i ] ] dp[i][j-v[i]] dp[i][j−v[i]]要求对应本层i的值,也就是让 d p [ j − v [ i ] ] dp[j-v[i]] dp[j−v[i]]先更新再更新 d p [ j ] dp[j] dp[j],所以把j从大到小循环改为从小到大循环即可。
优化后的代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,dp[1005];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n,m,v[1005],w[1005];
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++){
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[m];
}
多重背包
题目(数据范围小):https://www.acwing.com/problem/content/4/
朴素做法(三维):
多一维遍历第i个物品选 0 ~ s [ i ] 0~s[i] 0~s[i]个的所有情况
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
int v[105],w[105],s[105],dp[105]={0};
//memset(dp,0,105);
for (int i = 1; i <=n ; ++i) {
cin>>v[i]>>w[i]>>s[i];
}
for (int i = 1; i <=n ; ++i) {
for (int j = m; j >=v[i] ; --j) {
//cout<<dp[j]<<' ';
for (int k = 1; k <=s[i] ; ++k) {
if(j>=k*v[i])dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
}
}
//cout<<'\n';
}
cout<<dp[m]<<endl;
// for (int i = 0; i <=m ; ++i) {
// cout<<dp[i]<<' ';
// }
}
题目(数据范围大):https://www.acwing.com/problem/content/5/
思考到多重背包问题可以将每个物品拆开然后重新存入数组里,但如果直接拆开根据数据范围 s ∗ N ∗ V = 2000 ∗ 1000 ∗ 2000 = 2 ∗ 1 0 9 s*N*V=2000*1000*2000=2*10^9 s∗N∗V=2000∗1000∗2000=2∗109
还是会超过时间限制,所以考虑到将物品通过二进制的方法拆解:假如某物品有7件用1,2,4就可以表示全部可能。所以得出s件物品只需要 ⌈ l o g ( s ) ⌉ \lceil log(s) \rceil ⌈log(s)⌉的时间,所以就是 11 ∗ 1000 ∗ 2000 ≈ 2 ∗ 1 0 7 11*1000*2000\approx2*10^7 11∗1000∗2000≈2∗107,可以在规定时间内通过。但在具体实现中还会有问题,假如某物品有10件,不能直接用1,2,4,8表示,因为1,2,4,8可以表示1~15,而11~15不是我们所需要的。所以采取一个策略:用s减去1,2,4…当s<0的时候停止,并将最后剩余的那个数存进数组。假如10, 10 − 1 − 2 − 4 = 3 10-1-2-4=3 10−1−2−4=3,所以就将1,2,4,3存入数组。
#include <iostream>
#include <vector>
using namespace std;
struct struc {
int v, w;
};
int v[2005], w[2005], s[2005], dp[2005];
vector<struc> vec; //用结构体矢量存拆开的物品的数据
int main() {
ios::sync_with_stdio(false);
int n, m;
cin >> n >> m;
//memset(dp,0,105);
for (int i = 1; i <= n; ++i) {
cin >> v[i] >> w[i] >> s[i];
}
for (int i = 1; i <= n; ++i) {
for (int k = 1; k <= s[i]; k *= 2) {
s[i] -= k;
vec.push_back({k * v[i], k * w[i]});
//cout<<k<<' '<<"s[i]"<<s[i]<<' ';
}
//cout<<endl;
if (s[i] > 0)vec.push_back({s[i] * v[i], s[i] * w[i]}); //将剩下的那个数存进矢量
}
for (int i = 0; i < vec.size(); ++i) {
for (int j = m; j >= vec[i].v; --j) {
dp[j] = max(dp[j], dp[j - vec[i].v] + vec[i].w);
}
}
cout << dp[m] << endl;
}
分组背包
题目:https://www.acwing.com/problem/content/9/
解析:
代码:
#include<iostream>
#define pb push_back
using namespace std;
typedef long long ll;
int n,m,dp[105],s[105],v[105][105],w[105][105];
int main() {
cin>>n>>m;
for (int i = 1; i <=n ; ++i) {
cin>>s[i];
for (int j = 1; j <=s[i] ; ++j) {
cin>>v[i][j]>>w[i][j];
}
}
for (int i = 1; i <=n ; ++i) {
for (int j = m; j >=0 ; --j) {
for (int k = 1; k <=s[i] ; ++k) {
if(v[i][k]<=j)dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]); //遍历第i组物品中的所有可能
}
}
}
cout<<dp[m];
}