目录
- 动态规划的引入(01背包问题)
- 01背包问题的空间优化
- 完全背包问题
- 相关练习题
以下是正文:
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(1≤i≤V)进行决策 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) fi−1,j−k(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,j−k=fi−1,j−k.
所以我们可以将 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背包:
完全背包:
(完全背包的题有点不好找QWQ)
NOTE:换了个红轴键盘,打字舒服多了.
JiansYuan
2020.8.15