动态规划篇——背包问题

  • 背包问题


  • 0-1 背包

定义

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

例题

[USACO07DEC] Charm Bracelet S

例题分析

分析

f ( i , j ) f(i,j) f(i,j) 表示考虑前 i i i 件物品,最大容量为 j j j 的背包能包含物品价值的最大值。与此同时,设第 i i i 件物品的重量与价值分别为 w j w_j wj v j v_j vj。考察当前状态:当前状态一定是由前 i − 1 i-1 i1 件物品对应的状态转移而来。对于第 i i i 件物品,有放与不放两种状态。据此分析,我们可以写出状态转移方程(背包容量不能为负,所以当 j < w i j<w_i j<wi 的时候只能 f ( i , j ) = f ( i − 1 , j ) f(i,j) = f(i-1,j) f(i,j)=f(i1,j)):
f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − w i ) + v i ) f(i,j) = max(f(i-1,j),f(i-1,j-w_i)+v_i) f(i,j)=max(f(i1,j),f(i1,jwi)+vi)
因为每一次的状态转移中,我们都只需要比当前物品数量少 1 1 1 件的状态,为了减少空间开销,我们可以将状态转移方程压为一维的(此时 f ( j ) f(j) f(j) 表示最大容量为 j j j 的背包能包含的物品的最大值):
f ( j ) = m a x ( f ( j ) , f ( j − w i ) + v i ) f(j) = max(f(j),f(j-w_i)+v_i) f(j)=max(f(j),f(jwi)+vi)
但是,这个时候,对物品与对容量的遍历顺序就需要注意:因为我们能够将二维 DP 压至一维的原理是利用尚未更新的信息。所以,我们应该在外层循环遍历物品,也就是依次更新前 1 1 1 件,前 2 2 2 件……前 n n n 件物品的状态,并在内层循环更新对于前 i i i 件物品,容量不同的状态。注意到在二维的状态转移方程中,我们需要 f ( i − 1 , j ) f(i-1,j) f(i1,j) f ( i − 1 , j − w i ) f(i-1,j-w_i) f(i1,jwi) 来更新 f ( i , j ) f(i,j) f(i,j) ,也就是需要尚未更新的 f ( j ) f(j) f(j) f ( j − w i ) f(j-w_i) f(jwi) 来更新 f ( j ) f(j) f(j)。所以,我们应该从后往前遍历背包容量,保证 f ( j ) f(j) f(j) 一定早于 f ( j − w i ) f(j-w_i) f(jwi) 更新(因为 j > j − w i j>j-w_i j>jwi)。

代码

#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 ) = m a x ( f ( i − 1 , j ) , f ( i , j − w i ) + v i ) f(i,j) = max(f(i-1,j),f(i,j-w_i)+v_i) f(i,j)=max(f(i1,j),f(i,jwi)+vi)
类似地,将其压为一维 DP,状态转移方程即为:
f ( j ) = m a x ( f ( j ) , f ( j − w i ) + v i ) f(j) = max(f(j),f(j-w_i)+v_i) f(j)=max(f(j),f(jwi)+vi)
不同的是,在完全背包问题中,我们需要使用 f ( i − 1 , j ) f(i-1,j) f(i1,j) f ( i , j − w i ) + v i ) f(i,j-w_i)+v_i) f(i,jwi)+vi) 来更新 f ( i , j ) f(i,j) f(i,j),也就是未更新的 f ( j ) f(j) f(j)已经更新的 f ( j − w i ) f(j-w_i) f(jwi) 来更新 f ( j ) f(j) f(j)。所以,我们应该从前往后遍历背包容量,保证 f ( j ) f(j) f(j) 一定晚于 f ( j − w i ) f(j-w_i) f(jwi) 更新(因为 j > j − w i j>j-w_i j>jwi)。

代码

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值