动态规划(二)

01背包问题

01 背包-问题描述:
有 N 件物品和一个容量为 V 的背包。 第 i 件物品体积是 Wi,价值是Vi。
求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且
总价值最大 。
特点:每种物品仅有一件,可以选择放或不放 (对应 1 或 0)。
样例
3 10
<5,200>
<4,100>
<7,300>
Ans = 300 (前两个或只选第三个)

  • 设计状态:f i,j 表示仅考虑前i件物品,放入一个容量为 j 的背包,可以获得的最大价值
  • 状态转移方程
  • f i,j = max( f i-1,j , f i-1, j-wi + vi )
  • 前 i 件物品放入容量为 j 的背包中, 考虑第 i 件物品的策略(放或者不放)
  • 如果选择不放,问题转化为“前 i - 1 件物品放在容量为 j 的背包中”,也就是 f[i-1][j] 所表示的子问题的状态
  • 如果选择放,那么问题转化程“前 i - 1 件物品放入容量为 j - w[i] 的背包中”, 此时能活的最大价值的就是 f[i-1][j - w[i] + v[i]
  • 两种策略,取价值更大的
  • 答案是 f[n][v]

复杂度

时间复杂度 O(NxV)
空间复杂度O(NxV)
时间复杂度已经不能再优化了,但是可以用滚动数组来优化
优化之前的0-1背包

for (int i =1;i <=N;i++)
	for(int j = 0;j <=V;j++)
	{
		f[i][j] = f[i-1][j];
	 	if(j - w[i] >0)//能放得下
	 		f[i][j] = max(f[i][j], f[i-1][j-w[i]] + v[i]);
	}

在更新f[i][j]的信息的时候仅用到了上一层 f[i-1]的信息
而且,在更新f[i ] [ j ] 的时候,用到了上一层中,f [i][j - w[i]] 的信息。 由于 j - w[i] < j 因此在滚动数组的第二层进行更新的时候要逆序。

for (int i = 1;i ,= N;i++)
	for(int j = V; j>= 0;j--)
	if(j >= w[i])
	f[j] = max(f[j] ,f[j-w[i]] + v[i]);

滚动数组的关键是逆序

完全背包

有N种物品,和一个容量为V的背包,第i件物品的体积是Wi ,价值是 Vi
求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且总价值最大 。
特点:每种物品有无数件,可以选择 0 或多件

状态设计

按照0-1背包的思路 f[i][j]表示前i种物品放进一个容量为j的背包
状态转移
考虑一种物品的时候,不是常数的复杂度了,而是要考虑将一个物品放入多次

一个非常容易想到的优化:
加入现在有两件物品i ,j, 满足: Wi <= Wj 且Vi >=Vj
根据物美价廉,就可以将物品 j 去调

回想在0-1背包中,用滚动数组的时候要逆序
如果不是逆序的

for(int i=1;i <=N;i++)
	for(int j=0;j <= V;j++)
		if(j > w[i])
		f[j] = max(f[j], f[j - w[i]] + v[i]);
ans = f[V];

假设在考虑第一件物品的时候,w[1] = 2, v[1] = 3;
f[ 0 ] = = 0; f[ 2] = 3, f [ 4 ] = 6, f[ 6] = 9,
f[4] 是第一件物品被拿了两次的结果, f[6] 是第一件物品被拿了三次的结果。
这正好的符合多重背包的要求
0-1背包使用滚动数组要逆序,而多重背包改成顺序就可以了

时间复杂度 O(NxV) 空间复杂度O(V)

多重背包

有 N 件物品和一个容量为 V 的背包。 第 i 件物品体积是 Wi,价值是Vi
有 Ci 件可用,求解将哪些物品装入背包可使这些物品的体积总和不超过
背包容量,且总价值最大。
特点:每个物品只有有限件
设计状态:f[i][j] 表示前 i 种物品恰放入一个容量为 V 的背包的最大权值
状态转移方程
因为有个数限制,不采用完全背包的方式
通过二进制拆分,转换成0-1背包的问题

对于第 i 种物品,给定 Ci 个,可以从中选择 0-C i
如果不拆的话,相当于分成了 Ci 组物品,每组物品一个,通过对于每组进行选、不选的决策来确定最终选多少个(对 ∑Ci 组物品做 01 背包),导致组数太多,影响效率
考虑减少组数
13=(1101)2 → 1=(001)2 ,2=(010)2 , 4=(100)2 , 6=(110)2
将十三组减少为4组, 1~13中的任意一个数都可以被组合出来

//二进制拆分
int cnt = =0;
for(int i = 1;i <= N;i++)
{
	int t = c[i];
	for(int k = 1;k <= t;k <<1)
	{
		cnt++;
		//相当于形成了新的物品
		vv[cnt] = k*v[i];
		ww[cnt] = k*w[i];
	}
	if(t > 0)
	{
	 cnt ++;
	 vv[cnt] = t*v[i];
	 ww[cnt] = t*w[i];
	}
}

二进制拆分优化,时间复杂度
这样第 i 种物品我们就分成了 O(log Ci) 种物品
原问题的时间复杂度降为 O(V×∑logCi)) 的 01 背包问题
已经有了很大的改进

分组背包

有 N 件物品和一个容量为 V 的背包,第 i 种物品的体积是 Wi ,价值 Vi,将所有物品划分成若干组,每个组里面的物品最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大 。
特点是:每种物品有 1 件,每组只能选 1 件。
状态定义 f[k][j] 表示前K组,容量不超过 j 的最大价值
状态转移方程
状态转移方程

for(int k = 1;k <= ts;k++)//循环每一组
	for(int i = m; i>= 0;i--)//循环容量
		for(int j = 0; j < cnt[k];j++)
		if(i > w[k][j])
		f[i] = max(f[i], f[i - w[k][j] + v[k][j]);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值