寒假训练营 第十五节 动态规划(二)总结

背包 背包算法就是一种典型的从规模1推导到规模N的算法,是最常见的一种DP算法。它的核心要素有三个:背包容量,物品重量(或体积),物品价值,题目一般会要求在背包容量限制下获取最大价值。最简单的背包问题,有N件物品,每件物品都有一个重量和价值,在背包空间有限情况下如何选择物品,使得价值最大。
背包问题:
1.最典型、最基本的DP问题;
2.理解并熟练掌握背包问题意义重大;
3.DP问题中“状态”概念的理解;
4.背包的每个容量就是“状态”(思考一下杯子倒水问题中的状态转移);
背包问题主要有四种基础类型:01背包、完全背包、分组背包、多重背包。

01背包:(最基础的背包问题)

定义:有N件物品和一个容量为V的背包。第i件物品的体积是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
特点:每种物品仅有一件,可以选择放或不放。
状态转移方程:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}(需要放置的是第i件物品,这件物品的体积是c[i],价值是w[i],因此f[i-1][v]代表的就是不将这件物品放入背包,而f[i-1][v-c[i]]+w[i]则是代表将第i件放入背包之后的总价值,比较两者的价值,得出最大的价值存入现在的背包之中。)

对于背包问题,通常的处理方法是搜索。但是在这过程中我们发现,因为这些计算过程中会出现重叠的结点,符合动态规划中子问题重叠的性质。那么同时,可以看出如果通过第N次选择得到的是一个最优解的话,那么第N-1次选择的结果一定也是一个最优解。这符合动态规划中最优子问题的性质。
那么用动态规划的方法来解决,这里的:
阶段:在前N件物品中,选取若干件物品放入背包中
状态:在前N件物品中,选取若干件物品放入所剩空间为W的背包中的所能获得的最大价值
决策:第N件物品放或者不放
从而我们可以写出动态转移方程:f[i][j] = max(f[i - 1][j - W[i]] + P[i], f[i - 1][j]);//j >= W[ i ]
(我们用f[i][j]表示在前 i 件物品中选择若干件放在已用空间为 j 的背包里所能获得的最大价值)
(详解:将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[v];如果放第i件物品,那么问题就转化为“前i-1件物品放入已用的容量为c的背包中”,此时能获得的最大价值就是f[c]再加上通过放入第i件物品获得的价值w。
这样,我们可以自底向上地得出在前M件物品中取出若干件放进背包能获得的最大价值,也就是f[m,w])

1、题目详情:洛谷P1048 [NOIP2005 普及组] 采药

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”如果你是辰辰,你能完成这个任务吗?

	f[i, j]
			1. 集合:从前 i 种物品中选,(每种物品最多 1), 总体积不超过 j 的所有选法的集合
					属性:max 
			2. 计算:
						f[i, j] 一定是以下两种情况种的一种
						1. 选了 第 i 件物品  f[i, j] = f[i - 1, j - vi] + wi
						2. 没选第 i 件物品   f[i, j] = f[i - 1, j]
						f[i, j] = max(f[i - 1, j - vi] + wi, f[i - 1, j]);
			3. 初始化(边界)
					f[0, i] = 0
			4. 时间复杂度 
					O(n^2) 
#include<bits/stdc++.h>
using namespace std;
const int N = 110, M = 1010;
int f[M];
int v[N], w[N];
int main(){
	int n, m;
	cin >> m >> n;
	for (int i = 1; i <= n; i ++ ) 
	cin >> v[i] >> w[i];
	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]);	 
	cout << f[m] << endl;
	return 0;
}

注意: 01背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最基本思想,另外,别的类型的背包问题往往也可以转换成01背包问题求解。

完全背包:

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第 i 种物品的体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
基本思路:类似于01背包问题,所不同的是每种物品有无限件,也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……取[V/c]件等很多种。如果仍然按照解01背包时的思路,令f[v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程:f[j]=max{f[j],f[j-kc]+kw}(0<=k*c<=v)

2、题目详情:洛谷P1616 疯狂的采药

LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”如果你是 LiYuxiang,你能完成这个任务吗?
此题和原题的不同点:
1 1 1. 每种草药可以无限制地疯狂采摘。
2 2 2. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

f[i, j]
			
			1. 集合:从前 i 种物品中选,(每种物品无限 件), 总体积不超过 j 的所有选法的集合
					属性:max 
			2. 计算:
						f[i, j] 一定是以下情况的一种
						
						第 i 件物品选 几件
						0, 1, 2, ... k 
						k * vi <= j 	
	f[i, j] = max(f[i - 1, j], f[i - 1, j - vi] + wi, f[i - 1, j - 2vi] + 2wi... k)
	f[i, j - vi] = max(f[i - 1, j - vi], f[i - 1, j - 2vi] +  wi,... k) 				
			f[i, j] = f[i - 1, j]
			f[i, j] = f[i, j - vi] + wi;	
			3. 初始化(边界)
					f[0, j] = 0
			4. 时间复杂度 
					O(n^3)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10, M = 1e7 + 10;
long long f[M];
int v[N], w[N];
int main(){
	int n, m;
	cin >> m >> n;
	for (int i = 1; i <= n; i ++ ) 
		cin >> v[i] >> w[i];
	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]);
	cout << f[m] << endl;
	return 0;		 
}
3、题目详情:洛谷U279964 取硬币

现在有 n1+n2 种面值的硬币,其中前 n1种为普通币,可以取任意枚,后 n2 种为纪念币,每种最多只能取 1枚,每种硬币有一个面值,问能用多少种方法拼出 m 的面值?

	f[i, j]
			1. 集合
					(1) 从前 i 种物品中选, 总v 恰好等于 j 的所有选法的集合
						集合中选法的count 
			2. 计算集合 
						f[i, j]
						1. 1 ~ n1  // 完全 
							f[i, j] += f[i - 1, j] 
							f[i, j] += f[i, j - vi]
						2. n1 + 1 ~ n1 + n2 // 01
							f[i, j] += f[i - 1, j - vi]
							f[i, j] += f[i - 1, j]
			3. 边界(初始化)
						f[i, 0] = 1;
			4. 时间 O(n^2);
#include<bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7, N = 110, M = 1e5 + 10;
int f[M];
int v[N];
int main(){
	int n1, n2, m;
	cin >> n1 >> n2 >> m;
	for (int i = 1; i <= n1; i ++ ) cin >> v[i];
	for (int i = n1 + 1; i <= n1 + n2; i ++ ) cin >> v[i];
	for (int i = 0; i <= n1 + n2; i ++ ) f[0] = 1; 
	for (int i = 1; i <= n1; i ++ )
		for (int j = v[i]; j <= m; j ++ )
			f[j] = (f[j] + f[j - v[i]]) % MOD;
	for (int i = n1 + 1; i <= n1 + n2; i ++ )
		for (int j = m; j >= v[i]; j -- )
			f[j] = (f[j] + f[j -v[i]]) % MOD;
	cout << f[m];
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值