DP 复习

背包#

约定使用 vi 表示放入第 i 件物品的花费,wi 表示第 i 件物品的价值,背包容量 M,物品件数 N

01 背包#

每种物品仅有一件,可以选择放或不放。

f(i,j) 表示前 i 件物品恰填满容量为 j 的背包可以获得的最大价值。则其状态转移方程便是:

f(i,j)=max{f(i1,j),f(i1,jvi)+wi}

若放第 i 件物品,那么问题就转化为“前 i1 件物品放入剩下的容量为 jvi 的背包中”,此时能获得的最大价值就是 f(i1,jvi) 再加上通过放入第 i 件物品获得的价值 wi
如果不放第 i 件物品,那么问题就转化为“前 i1 件物品放入容量为 j 的背包中”,价值为 f(i1,j)

可以用滚动数组优化掉第一维,但注意要逆序枚举容量以达到“每种物品只能取一件”的限制。

for (int i = 1; i <= n; ++ i) {
	for (int j = m; j >= v[i]; -- j) {
		f[j] = max(f[j], f[j - v[i]] + w[i];
	}
}

若要求 恰好装满,则应将 f(1M) 初始化为 ,因为如果要求背包恰好装满,那么此时只有容量为 0 的背包可以在什么也不装且价值为 0 的情况下被“恰好装满”,反之,如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为 0,所以初始时状态的值也就全部为 0 了。

完全背包#

与 01 背包相似,但每种物品有无限件。

f(i,j)=max{f(i1,j),f(i,jvi)+wi}

可以用滚动数组优化掉第一维,但注意要 顺序 枚举容量以达到“每种物品可以取无限件”的限制。

完全背包的特点是每种物品可选无限件,所以在考虑“加选一件第 i 种物品”这种策略时,正需要一个可能已选入第 i 种物品的子结果 f(i,jvi),所以就可以并且必须采用 j 递增的顺序循环。

for (int i = 1; i <= n; ++ i) {
	for (int j = v[i]; j <= m; ++ j) {
		f[j] = max(f[j], f[j - v[i]] + w[i];
	}
}

多重背包#

与 01 背包相似,但每种物品最多取 ki 件。

二进制拆分每个物品后跑 01 背包即可。

for (int i = 1; i <= n; ++ i)
{
	cin >> a >> b >> m;
 	for (int j = 1; m - j >= 0; j <<= 1)  //二进制拆分优化
	{
		++ cnt;
		v[cnt] = a * j;
		w[cnt] = b * j;  
		m -= j;
	}
 	if (m)
	{
		++ cnt;
		v[cnt] = a * m;
		w[cnt] = b * m;
	}
}

混合背包#

有的物品只有一个,有的物品有无限个,还有的物品有有限个。

缝合怪,把前三种代码缝合起来即可。

二维费用背包#

对于每件物品,具有两种不同的费用,选择这件物品必须同时付出这两种费用。对于每种费用都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。

设第 i 件物品所需的两种费用分别为 civi。两种费用可付出的最大值(也即两种背包容量)分别为 CV。物品的价值为 wi

费用加了一维,只需状态也加一维即可。设 f(i,j,k) 表示前 i 件物品付出两种费用分别为 jk 时可获得的最大价值。状态转移方程就是:

f(i,j,k)=max{f(i1,j,k),f(i1,jci,kvi)+wi}

仍然可以滚动数组,按照背包类型确定枚举顺序即可。

有时,“二维费用”的条件是以“最多只能取 U 件物品” 来给出的。这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为 1,可以付出的最大件数费用为 U。

另一种看待二维背包问题的思路是:将它看待成复整数域上的背包问题。也就是说,背包的容量以及每件物品的费用都是一个复整数。而常见的一维背包问题则是自然数域上的背包问题。所以说,一维背包的种种思想方法,往往可以应用于二位背包问题的求解中,因为只是数域扩大了而已。

分组背包#

N 件物品和一个容量为 V 的背包。第 i 件物品的费用是 vi,价值是 wi。这些物品被划分为 K 组,每组最多选一件物品。

这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。

f(i,j) 表示前 f 组物品花费费用 j 能取得的最大权值,则有:

f(i,j)=max{f(i1,j),f(i1,jvk)+wk}

其中 k 是组 i 内的一件物品。

for (int i = 1; i <= K; ++ i)
	for (int j = V; j >= 0; -- j)
		for (int k : group[i])
			// 遍历组 i 的每一件物品
			if (j - v[k] >= 0)
				f[j] = max(f[j], f[j - v[k]] + w[k];

有依赖的背包#

其实是树形 DP。

咕咕咕。

泛化物品#

在背包容量为 V 的背包问题中,泛化物品是一个定义域为 {xZ0xV} 的函数 h,当分配给它的费用为 v 时,能得到的价值就是 h(v)

如果给定了两个泛化物品 hl,要用一定的费用从这两个泛化物品中得到最大的价值,这个问题怎么求呢?事实上,对于一个给定的费用 v,只需枚举将这个费用如何分配给两个泛化物品就可以了。同样的,对于 [0,V] 中的每一个整数 v,可以求得费用v 分配到 hl 中的最大价值 f(v)。也即

f(v)=max{h(k)+l(vk)0kv}

可以看到,这里的 f 是一个由泛化物品 hl 决定的定义域为 {xZ0xV} 的函数,也就是说, f 是一个由泛化物品 hl 决定的泛化物品。

本文所有代码均未经过编译。
大部分参考 崔天翼《背包九讲》。

Written with StackEdit.

作者:Hszzzx

出处:https://www.cnblogs.com/Hszzzx/p/dp-review.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   HyperV  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示