文章目录
小标题的超链接为原题链接,点击跳转
4. 动态规划(二)背包问题
背包也属于简单基础DP,单独拎出来讲
根据维基百科,背包问题(Knapsack
problem)是一种组合优化的NP完全(NP-Complete,NPC)问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。NPC问题是没有多项式时间复杂度的解法的,但是利用动态规划,我们可以以伪多项式时间复杂度求解背包问题。
———————————————— 版权声明:本文为CSDN博主「yuyan_jia」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yuyan_jia/article/details/119817834
要点:
- 分组背包,多重背包都是01背包的拓展
- 所有背包问题都是先枚举
物品种类i
再枚举体积j
,如果有k则枚举k - 完全背包问题的滚动数组形式,j是从前往后枚举,而01背包(包括分组和多重背包)的j则是从后往前枚举,原因取决于状态转移方程的方向
4.1 0-1背包
luogu1048采药
题目
考点:
二维dp
:一个花费一个价值的
本题也可以用滚动数组
压缩状态至一维dp
代码
// 采药
#include <iostream>
using namespace std;
const int N = 110, T = 1010;
int dp[N][T];
int t, n;
int w[N], v[N];
int main()
{
cin >> t >> n;
for(int i=1; i<=n; i++) cin >> w[i] >> v[i];
for(int i=1; i<=n; i++)
{
for(int j=0; j<=t; j++)
{
if(w[i] > j) dp[i][j] = dp[i-1][j];
else dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]);
}
}
cout << dp[n][t];
}
Acwing1022宠物小精灵之收服
题目
考点:
二维dp
+滚动数组
:题目中给出的是两个花费,一个价值,需要用滚动数组的思想将三维dp压缩状态至二维dp
代码
// 宠物小精灵之收服
#include <iostream>
using namespace std;
const int N = 1E3+10, M = 5E2 + 10, K = 1E2 + 10;
int dp[N][M];
int v1[N], v2[M];
int n, m, k;
int main()
{
cin >> n >> m >> k;
int topm = m--; // 难点:保留最大生命值,但是最大生命值不可用,因为皮卡丘死亡没有计算意义
for(int i=1; i<=k; i++)
{
int v1, v2;
cin >> v1 >> v2;
for(int j=n; j>=0; j--)
for(int k=m; k>=0; k--)
if(v1 <= j && v2 <= k) dp[j][k] = max(dp[j][k], dp[j-v1][k-v2]+1);
}
cout << dp[n][m] << " ";
int ans = m;
while(ans > 0 && dp[n][ans-1] == dp[n][m]) ans--;
cout << topm - ans;
}
4.2 完全背包
acwing3完全背包问题
题目
代码
#include<iostream>
using namespace std;
const int N = 1e3 + 10;
int dp[N][N];
int main()
{
int n, V;
cin >> n >> V;
for(int i=1; i<=n; i++)
{
int vi, wi;
cin >> vi >> wi;
for(int j=0; j<=V; j++)
{
if(vi <= j)
dp[i][j] = max(dp[i-1][j], dp[i][j-vi] + wi);
else dp[i][j] = dp[i-1][j];
}
}
cout << dp[n][V];
}
4.3 分组背包
acwing9分组背包问题
题目
代码
// acwing9分组背包问题
#include <iostream>
using namespace std;
const int N = 1e2+10;
int S[N], v[N][N], w[N][N];
int dp[N];
int main()
{
int n, m;
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++) // 这里j和k的枚举顺序不能替换
if(j >= v[i][k]) dp[j] = max(dp[j], dp[j-v[i][k]] + w[i][k]);
cout << dp[m];
}
4.4 多重背包
acwing4多重背包问题Ⅰ
题目
代码
#include<iostream>
using namespace std;
const int N = 1E2 + 10;
int dp[N][N];
int main()
{
int n, V;
cin >> n >> V;
for(int i=1; i<=n; i++)
{
int vi, wi, si;
cin >> vi >> wi >> si;
for(int j=0; j<=V; j++)
{
for(int k=0; k<=si; k++)// 将每种物品取0,1,2,..,k个共k+1个都当作一个种类
{
// 这里要注意k一定要囊括0,即k属于[0, si],因为k=0时实现了dp[i][j] = dp[i-1][j]
// 此外这里max函数第一个和01背包不同的在于是dp[i][j]而非dp[i-1][j],因为物品种类没有改变,只改变了取的数量k,这是由dp数组定义所决定的
if(vi*k <= j) dp[i][j] = max(dp[i][j], dp[i-1][j-vi*k] + wi*k);
}
}
}
cout << dp[n][V];
}
acwing5多重背包 Ⅱ
题目
代码
// acwing5多重背包问题II
#include <iostream>
using namespace std;
const int N = 11*2e3+10; // 11是log(2e3)好像
int dp[N];
int v[N], w[N];
int n, V;
int cnt = 0;
int main()
{
cin >> n >> V;
// 二进制分类
for(int i=1; i<=n; i++)
{
int vi, wi, si;
cin >> vi >> wi >> si;
int k = 1;
while(k <= si)
{
cnt++;
v[cnt] = vi * k;
w[cnt] = wi * k;
si -= k;
k *= 2;
}
if(si)
{
cnt++;
v[cnt] = vi * si;
w[cnt] = wi * si;
}
}
// 一维01背包模板
for(int i=1; i<=cnt; i++)
{
for(int j=V; j>=v[i]; j--)
{
dp[j] = max(dp[j], dp[j-v[i]] + w[i]);
}
}
cout << dp[V];
}