算法基础知识总结(动态规划)

五、动态规划

1、背包问题

1、0 1 背包

1、基本含义:有 N N N 个物品和一个容量是 V V V 的背包,每个物品的体积是 v i v_i vi,价值是 w i w_i wi每个物品最多只能用一次,即每个物品要么用 1 次,要么用 0 次 ,怎样装才能让总价值最大?

2、朴素解法:

在这里插入图片描述

3、优化解法:

通过观察状态转移方程,有两处可以优化:

(1) f ( i , j ) f(i, j) f(i,j) 只与 f ( i − 1 , ) f(i - 1, ) f(i1,) 相关,与更往前的状态无关,所以可以用一个一维数组来进行状态表示和状态计算。

(2)其次,在考虑包含第 i 个物品的时候采用了曲线救国的思路,它要求背包容量大于第 i 个物品的体积,所以在遍历所有可能的体积的时候可以直接从 v i v_i vi 开始遍历。但是,像这样从小到大遍历体积会出现问题。在更新 f ( i , j ) f(i, j) f(i,j) 的时候 f ( i − 1 , ) f(i - 1, ) f(i1,) 已经被更新过了,所以我们可以从大到小遍历体积。

4、代码注意点:

(1)由于定义了全局变量 f[N][N]所以对于 f[0][0~V]= 0可以不用初始化了。

(2)存储时从下标 1 开始比较好。

(3)包含 i 的情况只有当背包容量大于第 i 个物品的体积时才可能,如果这个条件不成立那么“一定包含第 i 个物品”的情况就不成立,那么就只有 f ( i , j ) = f ( f i − 1 , j ) f(i, j) = f(fi - 1, j) f(i,j)=f(fi1,j)这一步不能漏掉,得写在判断语句之前。

(4)最优解并不一定是背包装满的时候,但是最后输出的答案是 f[V]那是因为 f[v]一直在动态更新最大值,更新到了背包体积 V V V 结束。

2、完全背包

1、基本含义:有 N N N 个物品和一个容量是 V V V 的背包,每个物品的体积是 v i v_i vi,价值是 w i w_i wi每个物品可以用无限次 ,怎样装才能让总价值最大?

2、朴素解法:

在这里插入图片描述

3、优化一:

我们把状态转移方程展开:
f ( i , j ) = M a x ( f ( i − 1 , j ) , f ( i − 1 , j ) + f ( i − 1 , j − v i ) + w i + f ( i − 1 , j − 2 v i ) + 2 w i + ⋯   ) f(i, j) = Max(f(i - 1, j), f(i - 1, j) + f(i - 1, j - v_i) + w_i + f(i - 1, j - 2v_i) + 2w_i + \cdots) f(i,j)=Max(f(i1,j),f(i1,j)+f(i1,jvi)+wi+f(i1,j2vi)+2wi+)
观察展开后的结果,不禁让我们想要看看 f ( i , j − v ) f(i, j - v) f(i,jv) 展开后的样子:
f ( i , j − v i ) = M a x ( f ( i − 1 , j − v i ) , f ( i − 1 , j − 2 v i ) + w i + ⋯   ) f(i, j - v_i) = Max(f(i - 1, j - v_i), f(i - 1, j - 2v_i) + w_i + \cdots) f(i,jvi)=Max(f(i1,jvi),f(i1,j2vi)+wi+)
不难发现状态转移方程由此可以优化成: f ( i , j ) = M a x ( f ( i − 1 , j ) , f ( i , j − v i ) + w i ) f(i, j) = Max(f(i - 1, j), f(i, j - v_i) + w_i) f(i,j)=Max(f(i1,j),f(i,jvi)+wi)

这样就免去了遍历第 i 个物品选 k 个这一层循环。

4、优化二:

和 0 1背包问题一样可以优化成一维的。

5、代码注意点:

(1)完全背包问题和 0 1背包问题的状态转移方程很相似,一个重要的不同点在于完全背包问题包含第 i 个物品的情况不是从 i - 1转移过来的,所以在遍历体积的时候应该是从小到大

(2)在使用朴素解法时,要注意虽然每个物品可以用无限次但是在写 for 循环的时候停止条件是总体积不超过背包的体积。

3、多重背包

1、基本含义:有 N N N 个物品和一个容量是 V V V 的背包,每个物品的体积是 v i v_i vi,价值是 w i w_i wi每个物品的个数不一样 ,怎样装才能让总价值最大?

2、朴素版解法:
在这里插入图片描述

不难发现多重背包问题的朴素解法和完全背包问题的朴素解法很相似,只不过是把每个物品从无限个变成了 s i s_i si 个。

3、优化版解法:核心思想:二进制优化

现在的问题是有 N N N 个物品,每个物品的数量是 s i s_i si ,朴素做法需要遍历 s i s_i si 次。二进制优化的做法是将这 s i s_i si 个物品按照 2 n , n ≥ 1 2^n, n\geq 1 2n,n1 的方式分组,比方说第 i 个物品有 200 个,则分组为:1,2,4,8,16,32,64,128,72 不难证明这样分组之后依然能够涵盖 1 到 200 每个数。这样一来遍历的次数就降到了 l o g 2 s i log_2s_i log2si 次,更重要的是多重背包问题就优化成了 0 1背包问题!

4、代码注意点:

(1)分完组之后不要忘记更新 N

(2)开数组的时候应该是物品种类的总数乘最大的 l o g 2 s i log_2s_i log2si

4、分组背包

1、基本含义:有 N N N 组物品 和一个容量是 V V V 的背包,每个物品的体积是 v i v_i vi,价值是 w i w_i wi每组物品里最多只可以一个物品,怎样装才能让总价值最大?

2、解决思路:
在这里插入图片描述

和多重背包问题的集合划分很相似,不同点在于分组背包问题考察的是第 i 组物品选哪一个。1 ~ j 是第 i 组物品内的编号。

3、代码注意点:

(1)在倒序遍历体积的时候由于还没有确定是第 i 组物品中的哪一个,还不知道最小的体积应该是多少,所以可以直接遍历到 0,在确定了物品的序号之后再写一个判断语句即可。

2、线性DP

1、数字三角形

1、题目描述:给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

在这里插入图片描述

2、解题思路:

思路1:自上而下遍历数字三角形

在这里插入图片描述

思路二:自下而上遍历数字三角形

在这里插入图片描述

3、代码注意点:

(1)初始化时要把三角形外侧设置成负无穷。

(2)存储三角形时下标从 1 开始。

(3)如果是从上往下遍历,不要忘记初始状态的值更新成起点。则状态计算从第二层开始。

(4)如果从下往上遍历,可以免去初始化时对边界问题的处理,代码会简短很多。这种方法初始化时直接把状态数组的对应位置初始化成数字三角形对应位置的值。并且更新的时候在原先的状态值上累加。

2、最长上升子序列

1、题目描述:给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。

2、朴素版解法:

在这里插入图片描述

这道题目的状态是一维的。集合划分是根据第 i- 1 个数进行的,如果大于等于第 i 个数就不用更新状态值了。

3、优化版解法:

通过观察和思考可以发现,每个数最适合放在小于自己的最大的一个数后面。所以可以再创建一个序列 q q q,然后在遍历每个输入的同时动态地更新当前最长子序列。更新的方式就是对于每一个输入,到 q q q 中寻找小于自己的最大的一个数==(二分查找)==,并添加到它的后面。

4、代码注意点:

(1)朴素版解法记得要把每个数的状态值初始化成1,即只有自己的序列长度。

(2)优化版做法记得把 q[0]设置成哨兵。

3、最长公共子序列

1、题目描述:给定两个长度分别为N和M的字符串A和B,求既是A的子序列又是B的子序列的字符串长度最长是多少。

2、思路:

在这里插入图片描述

3、代码注意点:

(1)a[i], b[j]都在子序列的时候必须满足 a[i] = b[j]

(2)和之前的状态计算不同,这里的 f ( i − 1 , j ) ,   f ( i , j − 1 ) f(i - 1, j), \ f(i, j - 1) f(i1,j), f(i,j1) 并不是真正的 0 1 和 1 0 的情况,而是包含了这两种情况,并且还包含了第一种情况,所以在最后的方程中没有 0 0 的情况。

(3)输入的字符串,cin >> a[1]即可,不用写循环。

4、最短编辑距离

1、题目描述:给定两个字符串A和B,现在要将A经过若干操作变为B,可进行的操作有:

  1. 删除–将字符串A中的某个字符删除。
  2. 插入–在字符串A的某个位置插入某个字符。
  3. 替换–将字符串A中的某个字符替换为另一个字符。

现在请你求出,将A变为B至少需要进行多少次操作。

2、思路:

在这里插入图片描述

3、代码注意点:

(1)记得初始化只有删除和只有添加的情况。

3、区间DP

1、例题:石子合并:设有N堆石子排成一排,其编号为1,2,3,…,N。每堆石子有一定的质量,可以用一个整数来描述,现在要将这N堆石子合并成为一堆。每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相 同。找出一种合理的方法,使总的代价最小,输出最小代价。

2、思路:

在这里插入图片描述

3、代码注意点:

(1)f[l][r]要先初始化成一个比较大的数。

(2)共有三层循环

4、计数DP

1、例题:整数划分:一个正整数nn可以表示成若干个正整数之和,形如:n=n1+n2+…+nkn=n1+n2+…+nk,其中n1≥n2≥…≥nk,k≥1n1≥n2≥…≥nk,k≥1。我们将这样的一种表示称为正整数n的一种划分。现在给定一个正整数n,请你求出n共有多少种不同的划分方法。

2、思路:这道题目可以参考完全背包问题的思路,把 n n n 当作背包的体积,1 到 n n n 当作物品的体积。

在这里插入图片描述

同样,状态方程可以优化成: f ( i , j ) = f ( i − 1 , j ) + f ( i , j − v i ) f(i, j) = f(i - 1, j)+f(i, j - v_i) f(i,j)=f(i1,j)+f(i,jvi) ,并且写成一维的最简代码。

3、代码注意点:

(1)由于要求的是数量,所以应该累加。

5、数位统计DP

1、例题:计数问题(不会)

6、状态压缩DP

把状态用二进制数表示。

1、蒙德里安的梦i想

1、题意:求把NM的棋盘分割成若干个12的的长方形,有多少种方案。

例如当N=2,M=4时,共有5种方案。当N=2,M=3时,共有3种方案。

如下图所示:

2411_1.jpg

2、思路:把分割转换成往棋盘里放两种方向的长方形,可以只看横向的长方形应该怎么放。

在这里插入图片描述

本体的状态集合 f[i, j] 中 i 表示列,j 表示在第 i 列,有从前一列伸过来的长方形的所在行构成的一个 N 位二进制数。比如图中的第 i 列对应的 j 就是 0 0 0 0 1, 第 i - 1 列对应的 j 是 0 1 0 0 0,那么 f[i, j] 的含义就是当摆到第 i 列并且状态是 j 的所有摆法。

假设第 i 列的状态记作 j,第 i - 1 列的状态记作 k,则状态转移的条件是:① j & k = 0;② j | k 不存在连续奇数个0。第一点的意思是,如果 j & k != 0,那么必定存在某一行有格子重叠;第二点的含义是,只有当 j | k中连续偶数个0的时候才能放下竖着的长方形。

3、代码注意点:

(1)j | k 不存在连续奇数个0需要提前预处理出来。由于 j 和 k 都是 2 N 2^N 2N 位的二进制数,所以 j | k 也是 2 N 2^N 2N 位的二进制数。那么只需要先把这些数当中存在连续奇数个 0 的二进制数标记出来,即打表。在DP的时候查表即可。

(2)打表的时候,没遇到一次 1 就要判断一下是否前面累积的 0 的数量是奇数个,判断结束后要把累计的 0 的数量清空。

(3)需要注意状态值是从下标0开始的并且 f[0][0] = 1,意思是当且仅当第一列没有由上一列伸出来的横向长方形时才能够算一种方案。否则肯定是不对的。

(4)因为最后要求的是数量所以应该累加状态值

(5)最后的答案应该是 f[M][0],原因是,由于状态值的存储是从下标 0 开始的,所以 f[M][0]的意思是最后一列没有横向的长方形伸出来。

(6)DP 的时候要从状态数组下标 1 开始,到 M 结束。

(7)把一个数和 1 相与,可以判断该数的奇偶。

(8)由于答案可能很大,需要把状态数组定义成 long long

(9)每次循环之前要先把状态数组清零。

2、最短 Hamilton 路径

1、题意:给定一张 nn 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。

2、思路:

在这里插入图片描述

3、代码注意点:

(1)要把所有状态值初始化成无穷大,由于初始位置是在 0 号点,那么状态是 0000…0001 ,所以 f[1][0] = 1

7、树形DP

1、例题:没有上司的舞会:Ural大学有N名职员,编号为1~N。他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。每个职员有一个快乐指数,用整数 HiHi 给出,其中 1≤i≤N1≤i≤N。现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。

2、思路:

在这里插入图片描述

3、代码注意点:

(1)本题是用 dfs 实现的。

(2)dfs从根节点开始。

8、记忆化搜索

1、例题:滑雪:给定一个R行C列的矩阵,表示一个矩形网格滑雪场。矩阵中第 i 行第 j 列的点表示滑雪场的第 i 行第 j 列区域的高度。一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。

2、思路:

在这里插入图片描述

3、代码注意点:

(1)本题采用了偏移量技巧。

(2)要把状态值都初始化成 -1,表示每个状态都还没有被算过

(3)f[i][j]最小是 1 而不是 0, 表示至少可以走当前这个位置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值