动态规划基础(一)之01背包与完全背包问题

目录

  1. 动态规划的引入(01背包问题)
  2. 01背包问题的空间优化
  3. 完全背包问题
  4. 相关练习题

以下是正文:

1.动态规划的引入(01背包问题)

先来这一题:洛谷P1049

题目描述

有一个箱子容量为VV(正整数,0 ≤ \le V ≤ \le 200000≤V≤20000),同时有nn个物品(0<n ≤ \le 300<n≤30,每个物品有一个体积(正整数)。

要求nn个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

输入格式

11个整数,表示箱子容量

11个整数,表示有n个物品

接下来n行,分别表示这n个物品的各自体积

输出格式

11个整数,表示箱子剩余空间。

输入输出样例

输入 #1复制

24
6
8
3
12
7
9
7

输出 #1复制

0

在没了解动态规划前, 我们应该会想到用dfs或者dfs来解决这类题目.

但是我们既然要讲动态规划(Dynamic programming), 那就只能用动态规划来写.

我们用 f i , j f_i,_j fi,j来表示前i个物品中选择若干个, 使得总空间占用为j所产生的价值, V是箱子总容积.

然后就可以对于每个 a i ( 1 ≤ i ≤ V ) a_i(1\le i\le V) ai(1iV)进行决策 f i , j f_i,_j fi,j选或不选, 找到当前情况下 f i , j f_i,_j fi,j的最优解.

时空复杂度应该都是: O ( V N ) O(VN) O(VN).

code:

#include <bits/stdc++.h>
using namespace std;
const int M = 40;
const int T = 200005;
int f[T];
int w[M];
int main()
{
	memset(w, 0, sizeof w);
	memset(f, 0, sizeof f);
	int v, n;
	scanf("%d%d", &v, &n);
	for(int i = 0; i < n; i++) scanf("%d", &w[i]);
	for(int i = 0; i < n; i++)
		for(int j = v; j >= w[i]; j--)
			f[i][j] = max(f[i-1][j], f[i-1][j - w[i]] + w[i]);
	printf("%d", v - f[n-1][v]);
	return 0;
}

2. 01背包问题的空间优化

我们发现, 更新每次的 f i , j f_{i,j} fi,j只会用到 f i − 1 , j − k ( k > = 0 ) f_{i-1,j-k}(k>=0) fi1,jk(k>=0), 所以对于 f i , j f_i,_j fi,j, 若j从V开始每次减一, f i , j − k = f i − 1 , j − k f_{i, j-k} = f_{i-1,j-k} fi,jk=fi1,jk.

所以我们可以将 f i , j f_{i,j} fi,j化简为 f j f_j fj.

此时, 空间复杂度便优化至: O ( V ) O(V) O(V).

code:

#include <bits/stdc++.h>
using namespace std;
const int M = 40;
const int T = 200005;
int f[T];
int w[M];
int main()
{
	memset(w, 0, sizeof w);
	memset(f, 0, sizeof f);
	int v, n;
	scanf("%d%d", &v, &n);
	for(int i = 0; i < n; i++) scanf("%d", &w[i]);
	for(int i = 0; i < n; i++)
		for(int j = v; j >= w[i]; j--)
			f[j] = max(f[j], f[j - w[i]] + w[i]);
	printf("%d", v - f[v]);
	return 0;
}

3.完全背包问题

再看一题:洛谷P1616.

题目背景

此题为纪念 LiYuxiang 而生。

题目描述

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

如果你是 LiYuxiang,你能完成这个任务吗?

此题和原题的不同点:

\11. 每种草药可以无限制地疯狂采摘。

\22. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入格式

输入第一行有两个整数,分别代表总共能够用来采药的时间 tt 和代表山洞里的草药的数目 mm

第 22 到第 (m + 1)(m+1) 行,每行两个整数,第 (i + 1)(i+1) 行的整数 a_i, b_ia**i,b**i 分别表示采摘第 ii 种草药的时间和该草药的价值。

输出格式

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

输入输出样例

输入 #1复制

70 3
71 100
69 1
1 2

输出 #1复制

140

说明/提示

数据规模与约定
  • 对于 30%30% 的数据,保证 m \le 10^3m≤103 。
  • 对于 100%100% 的数据,保证 1 \leq m \le 10^41≤m≤104,1 \leq t \leq 10^71≤t≤107,且 m \times t < 10^7m×t<107,1 \leq a_i, b_i \leq 10^41≤a**i,b**i≤104。

这题很明显于上一题不同,这一题对于物品选择次数没有限制,所以一个物品有多种状态;而上一题一个物品之允许选或不选,只有两种状态.

其实,我们将01背包空间优化版的代码稍微修改,就得到了本题的代码.

我们发现,将:

for(int j = V; j >= w[i]; j--)

替换为:

for(int j = w[i]; j <= V; j++)

不难发现, 此时一个物品可以被选择多次.

时空复杂度不变, 见下:

时间复杂度: O ( V N ) O(VN) O(VN), 空间复杂度: O ( V ) O(V) O(V).

code:

#include <bits/stdc++.h>
using namespace std;
const int M = 10005;
const int T = 10000005;
int f[T];
int w[M], v[M];
int main()
{
	memset(v, 0, sizeof v);
	memset(w, 0, sizeof w);
	memset(f, 0, sizeof f);
	int V, n;
	scanf("%d%d", &V, &n);
	for(int i = 0; i < n; i++) scanf("%d%d", &w[i], &v[i]);
	for(int i = 0; i < n; i++)
		for(int j = w[i]; j <= V; j++)
			f[j] = max(f[j], f[j - w[i]] + v[i]);
	printf("%d", f[V]);
	return 0;
}

4.相关练习题

01背包:

洛谷P1048

洛谷P1049

洛谷P1060

完全背包:

(完全背包的题有点不好找QWQ)

洛谷P1616


NOTE:换了个红轴键盘,打字舒服多了.

JiansYuan
2020.8.15

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值