动态规划篇——背包问题

  • 背包问题


  • 0-1 背包

定义

0-1 背包问题的特点是物品只有取与不取两种状态,是最基础的背包问题。

例题

[USACO07DEC] Charm Bracelet S

例题分析

分析

\(f(i,j)\) 表示考虑前 \(i\) 件物品,最大容量为 \(j\) 的背包能包含物品价值的最大值。与此同时,设第 \(i\) 件物品的重量与价值分别为 \(w_j\)\(v_j\)。考察当前状态:当前状态一定是由前 \(i-1\) 件物品对应的状态转移而来。对于第 \(i\) 件物品,有放与不放两种状态。据此分析,我们可以写出状态转移方程(背包容量不能为负,所以当 \(j<w_i\) 的时候只能 \(f(i,j) = f(i-1,j)\)):

\[f(i,j) = max(f(i-1,j),f(i-1,j-w_i)+v_i) \]

因为每一次的状态转移中,我们都只需要比当前物品数量少 \(1\) 件的状态,为了减少空间开销,我们可以将状态转移方程压为一维的(此时 \(f(j)\) 表示最大容量为 \(j\) 的背包能包含的物品的最大值):

\[f(j) = max(f(j),f(j-w_i)+v_i) \]

但是,这个时候,对物品与对容量的遍历顺序就需要注意:因为我们能够将二维 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;
}

  • 完全背包

定义

完全背包的特点是每个物品可以无限取

例题

疯狂的采药

例题分析

分析

因为每个物品可以无限取,所以状态转移方程应改为:

\[f(i,j) = max(f(i-1,j),f(i,j-w_i)+v_i) \]

类似地,将其压为一维 DP,状态转移方程即为:

\[f(j) = max(f(j),f(j-w_i)+v_i) \]

不同的是,在完全背包问题中,我们需要使用 \(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;
}
posted @ 2023-08-16 01:45  susenyang  阅读(72)  评论(0)    收藏  举报  来源