动态规划篇——背包问题
-
背包问题
-
0-1 背包
定义
0-1 背包问题的特点是物品只有取与不取两种状态,是最基础的背包问题。
例题
例题分析
分析
设 \(f(i,j)\) 表示考虑前 \(i\) 件物品,最大容量为 \(j\) 的背包能包含物品价值的最大值。与此同时,设第 \(i\) 件物品的重量与价值分别为 \(w_j\),\(v_j\)。考察当前状态:当前状态一定是由前 \(i-1\) 件物品对应的状态转移而来。对于第 \(i\) 件物品,有放与不放两种状态。据此分析,我们可以写出状态转移方程(背包容量不能为负,所以当 \(j<w_i\) 的时候只能 \(f(i,j) = f(i-1,j)\)):
因为每一次的状态转移中,我们都只需要比当前物品数量少 \(1\) 件的状态,为了减少空间开销,我们可以将状态转移方程压为一维的(此时 \(f(j)\) 表示最大容量为 \(j\) 的背包能包含的物品的最大值):
但是,这个时候,对物品与对容量的遍历顺序就需要注意:因为我们能够将二维 DP 压至一维的原理是利用尚未更新的信息。所以,我们应该在外层循环遍历物品,也就是依次更新前 \(1\) 件,前 \(2\) 件……前 \(n\) 件物品的状态,并在内层循环更新对于前 \(i\) 件物品,容量不同的状态。注意到在二维的状态转移方程中,我们需要 \(f(i-1,j)\) 与 \(f(i-1,j-w_i)\) 来更新 \(f(i,j)\) ,也就是需要尚未更新的 \(f(j)\) 与 \(f(j-w_i)\) 来更新 \(f(j)\)。所以,我们应该从后往前遍历背包容量,保证 \(f(j)\) 一定早于 \(f(j-w_i)\) 更新(因为 \(j>j-w_i\))。
代码
#include <iostream>
using namespace std;
int dp[13000];
int w[3500], v[3500];
int main()
{
int N, M;
cin >> N >> M;
for (int i = 1; i <= N; i++)cin >> w[i] >> v[i];
for (int i = 1; i <= N; i++)
{
for (int j = M; j >= 1; j--)
{
if (j >= w[i])dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[M] << endl;
return 0;
}
-
完全背包
定义
完全背包的特点是每个物品可以无限取。
例题
例题分析
分析
因为每个物品可以无限取,所以状态转移方程应改为:
类似地,将其压为一维 DP,状态转移方程即为:
不同的是,在完全背包问题中,我们需要使用 \(f(i-1,j)\) 与 \(f(i,j-w_i)+v_i)\) 来更新 \(f(i,j)\),也就是未更新的 \(f(j)\) 与已经更新的 \(f(j-w_i)\) 来更新 \(f(j)\)。所以,我们应该从前往后遍历背包容量,保证 \(f(j)\) 一定晚于 \(f(j-w_i)\) 更新(因为 \(j>j-w_i\))。
代码
#include <iostream>
using namespace std;
long long dp[10000005];
long long w[10005], v[10005];
int main()
{
long long N, M;
cin >> M >> N;
for (long long i = 1; i <= N; i++)cin >> w[i] >> v[i];
for (long long i = 1; i <= N; i++)
{
for (long long j = 1; j <= M; j++)
{
if (j >= w[i])dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[M] << endl;
return 0;
}