DP(我的比较菜,很垃圾)

动态规划基础
清华大学 赵和旭



前言
1:我们先不对动态规划做一些枯燥的定义。
2:而是看两个简单的问题,看看如何分析和解决这样的问题,从这几道
题的分析过程共同点或者说特点中,归纳总结出动态规划的一般特点和
思路。
3:然后再通过几道例题来强化理解我们总结的动态规划做题思路。
4:之后我们将从另一角度理解动态规划,即记忆化搜索视角。
5:我们还将理解DP一种简单情况的转移路径, DAG上的DP
6:再通过大家独立思考,实践两道题目,实现对dp算法的初步运用。
2019/7/17 Wednesday 清华大学——赵和旭 2
前言
7:一般形式的背包dp
8:一般形式序列上的dp
9:一般形式的区间dp
2019/7/17 Wednesday 清华大学——赵和旭 3
填满网格
给出一个2*n的网格,你现在需要用n2*1的多米诺骨牌占满整个棋盘。
多米诺骨牌可以横着或者竖着放。
求有多少方案数占满整个棋盘。
N<=10^6
2019/7/17 Wednesday 清华大学——赵和旭 4
填满网格
f[n]n列的答案
则根据如何对最后一两列放多米诺分情况讨论可得
f[n]=f[n-1]+f[n-2]
这是递推,也算是一个简单的dp,只不过绝大多数dp的转移方程不是根
据这题一样简简单单的一个加号就解决的。
2019/7/17 Wednesday 清华大学——赵和旭 5
网格图路径计数
给出一个n*m的网格,每次只能向右或者向下走,求从(1,1)走到(n,m)的方
案数,其中有些位置是不能走的。
n,m<=1000
2019/7/17 Wednesday 清华大学——赵和旭 6
网格图路径计数
如果没有障碍物:设dp[i][j]表示从(1,1),走到(i,j)的方案数。
考虑上一步从哪里走过来可得, dp[i][j]=dp[i-1][j]+dp[i][j-1]
答案就是dp[n][m]
对于障碍:如果(i,j)是一个障碍,则定义dp[i][j]=0
2019/7/17 Wednesday 清华大学——赵和旭 7
走金字塔的最大价值路径
给出一个高度为n的金字塔,你现在要从金字塔的顶端走到金字塔的底端。
金字塔的第i层,第j房间(记为(i,j) )有价值为a[i][j]的宝藏,每一步能从(i,j)
走到, (i+1,j) , (i+1,j+1)。求从金字塔顶部走到底部能获得的最大的价值是多少?
n=4的一个例子。
7
3 8
8 1 0
2 7 4 4
ans=7+3+8+7=25
2019/7/17 Wednesday 清华大学——赵和旭 8
走金字塔的最大价值路径
和前面两题差不多。只不过前面是求方案数,运算法则为加法,而这里
求最优值,运算法则是取max
dp[i][j]表示从塔顶走到(i,j)能得到的最大的价值是多少。
dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+a[i][j]
2019/7/17 Wednesday 清华大学——赵和旭 9
动态规划的基本思想? ——最优化。
利用最优化原理把多阶段过程转化为一系列单阶段问题,利用各阶段之
间的关系,逐个求解。
更具体的,假设我们可以计算出小问题的最优解,那么我们凭借此可以
推出大问题的最优解,进而我们又可以推出更大问题的最优解。(要满
足最优子结构)
(从小问题答案推到大问题的答案)
而最小的问题也就是边界情况我们可以直接计算出答案来。
2019/7/17 Wednesday 清华大学——赵和旭 10
动态规划的基本思想? ——最优化。
基本思想是将待求解的问题分解为若干个子问题(阶段),按顺序求解
子阶段,小的子问题的解,为更大子问题的求解提供了有用的信息。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,
对每一个子问题只解一次。
就像刚刚那几个递推式我们如果递归来做就会有很多冗余的计算。
2019/7/17 Wednesday 清华大学——赵和旭 11
动态规划的状态
动态规划过程中,需要有状态表示和最优化值(方案值)
状态表示是对当前子问题的解的局面集合的一种(充分的)描述。
最优化值(方案值)则是对应的状态集合下的最优化信息(方案值),
我们最终能通过其直接或间接得到答案。
2019/7/17 Wednesday 清华大学——赵和旭 12
状态表示
对于状态的表示,要满足三条性质
1:具有最优化子结构:即问题的最优解能有效地从问题的子问题的最优
解构造而来
2:具有简洁性:尽可能的简化状态的表示,获得更优的时间复杂度。
3:同时能够全面的描述一个局面。一个局面有一个答案,而这个局面是
需要一些参数来描述的。
设计状态的关键就是 充分描述,尽量简洁。
2019/7/17 Wednesday 清华大学——赵和旭 13
动态规划的精髓? ——状态转移
由于具有最优化子结构(在最优化问题种),所以求当前状态的最优值
可以通过其他的(较小的问题)状态的最优值加以变化而求出。所以,
当一个状态的所有子结构都已经被计算之后,我们就可以通过一个过程
计算出他的最优值。这个过程就是状态的转移。
注意:状态的转移需要满足要考虑到所有可能性。
2019/7/17 Wednesday 清华大学——赵和旭 14
怎么计算动态规划的时间复杂度?
一般简单动态规划时间复杂度==状态数×状态转移复杂度。
同时也就引出了dp的两种优化时间的方法
1:减少状态的维数
2:加速状态转移,例如数据结构优化或者分析性质
2019/7/17 Wednesday 清华大学——赵和旭 15
接下来我将通过一道例题,来对上面所说的思想和精髓包括复杂度的计
算来进行解释。
2019/7/17 Wednesday 清华大学——赵和旭 16
最长上升子序列
给出一个长度为n的数列 a[1..n], 求这个数列的最长上升子序列。
就是给你一个序列,请你在其中求出一段不断严格上升的子序列,子序列是不
一定要求连续的。
2,3,4,72,3,4,6是序列2 5 3 4 1 7 6的两种选取方案。最长上升子序列的长度就
4
N<=1000
2019/7/17 Wednesday 清华大学——赵和旭 17
最长上升子序列
这是一道最为经典的完全用动态规划来解决的问题。
dp[i]为以a[i]为末尾的最长上升子序列的长度。
最后的答案就是我枚举一下最长上升子序列的结束位置,然后取一个最
大值即可。
问题是如何求这个数组?
那我们回过头来想一想最开始说的动态规划的基本思想,从小的问题推
出大的问题。
2019/7/17 Wednesday 清华大学——赵和旭 18
最长上升子序列
假设我们知道了dp[1..(i-1)],我们怎么才能根据这些信息推出来dp[i]
再次强化一下定义: dp[i]表示以i结尾的最长上升子序列。
我们只需要枚举这个上升子序列倒数第二个数是什么就好了。
状态转移: dp[i]=max{ dp[j] | a[j]<a[i] && j<i } +1 ;
2019/7/17 Wednesday 清华大学——赵和旭 19
最长上升子序列
之前的例子:
A{2 , 5 , 3 , 4 , 1 , 7 , 6}
dp[1]=1;
dp[2]=max{dp[1]}+1;
dp[3]=max{dp[1]}+1;
dp[4]=max{dp[1],dp[3]}+1;
时间复杂度 On^2)。
2019/7/17 Wednesday 清华大学——赵和旭 20
最长上升子序列
在这个问题的分析中,突破口?
1:设计出了dp[1..n]这个可以储存以i结尾的子序列中最优的答案,这个
状态表示。
2:通过分析问题成功能将当前状态的最优值能过由之前状态转移出来。
dp[i]=max{ dp[j] | a[j]<a[i] && j<i } +1 ,状态转移。
2019/7/17 Wednesday 清华大学——赵和旭 21
时间复杂度
套上之前的公式:状态数×状态转移复杂度。
状态数: dp[1..n],只有n个状态。
状态转移: dp[i]=max{ dp[j] | a[j]<a[i] && j<i } +1 ;
由于你每次都需要枚举之前的所有数,所以单次转移是On)。
所以总复杂度 On^2)。
2019/7/17 Wednesday 清华大学——赵和旭 22
最长上升子序列核心代码
dp[i]为以a[i]为末尾的最长上升子序列的长度。
状态转移: dp[i]=max{ dp[j] | a[j]<a[i] && j<i } +1 ;
2019/7/17 Wednesday 清华大学——赵和旭 23
P1091合唱队形
N 位同学站成一排,音乐老师要请其中的( N-K )位同学出列,使得剩下的
K 位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为 1,2,…
他们的身高分别为 T_1,T_2,…,T_K
则他们的身高满足 T1<T2<T3..<Ti>T_i+1> ...>Tk
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可
以使得剩下的同学排成合唱队形。
n<=100000
P1091合唱队形
我们设f[i]表示以i结尾的最长上升子序列长度。
我们设g[i]表示以i开头的最长下降子序列长度。
然后我们枚举哪一个为中心的最高点, f[i]+g[i]-1取最大值即可。
乘积最大
设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,
找出一种分法,使得这K+1个部分的乘积能够为最大。
有一个数字串: 312,当N=3K=1时会有以下两种分法:
1) 3*12=36
2) 31*2=62
这时,符合题目要求的结果是: 31*2=62
现在,请你帮助你的好朋友XZ设计一个程序,求得正确的答案。
NK6≤N≤801≤K≤50
2019/7/17 Wednesday 清华大学——赵和旭 26
乘积最大
f[i][a] 表示前 i 位数包含 a 个乘号所能达到的最大乘积,我们只需要枚
举上一个乘号所在的位置即可。
j a i - 1      进行一次枚举,表示前 j 位中含有 a-1 个乘号,且最后一个乘号的位置在
j 处。那么当最后一个乘号在 j 处时最大值为前 j 位中含有
a - 1 个乘号的最大值乘上 j 处之后到i的数字。
因此得出了状态转移方程 f[i][a] = max(f[i][a] , f[j][a-1] * cut(j + 1,i))——
cut(b + 1,i) 表示 b + 1 i 位数字)
然后再写个高精度即可。
2019/7/17 Wednesday 清华大学——赵和旭 27
基本上一般简单的dp题,就是先想想状态,然后再看能不能转移,如果不能转移,换种设状态的方式,或者增加一下状态的维数,使得局面描述的更加详细。


2019/7/17 Wednesday 清华大学——赵和旭 28
最长公共子序列
给定两个字符串ST,长度分别为nm,求解这两个字符串的最长公共子序列(
Longest Common Sequence)。
比如字符串SBDCABA;字符串TABCBDAB
则这两个字符串的最长公共子序列长度为4,最长公共子序列是: BCBA
n,m<=1000
2019/7/17 Wednesday 清华大学——赵和旭 29
最长公共子序列
我们设dp[i][j]表示, S串的第i个前缀和T串的第j个前缀的最长公共子序列。
分情况:
如果S[i]==T[j]dp[i][j]=dp[i-1][j-1]+1;
如果S[i]!=T[j]dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
最后答案就是dp[n][m]
2019/7/17 Wednesday 清华大学——赵和旭 30
最长公共子序列
做法很简单,主要是思路从何而来?
可用动态规划求解的问题,一般有两个特征:
1:最优子结构;(全局最优一定是子问题最优结合而得)
2:重叠子问题;(也就是如果是原先搜索的话要有不少冗余的计算,通过
dp我们每一个状态就计算一次,减少了复杂度,记忆话搜索本质是一样的)

本质来说我们就是要想办法把问题化小。
2019/7/17 Wednesday 清华大学——赵和旭 31
最长公共子序列
对于dp[i][j]
如果,两个串最后一个位置相同,这两个位置一定在公共子序列中。
那么我们只需要求出Si-1前缀和Tj-1前缀的最长上升子序列就可以了,而这个就是把问题化小。

如果最后一个位置不相同,那么两个位置一定不能匹配,所以肯定是另外两种情况选最大的。

2019/7/17 Wednesday 清华大学——赵和旭 32
记忆化搜索
2019/7/17Wednesday

清华大学——赵和旭 33

网格图路径计数
给出一个n*m的网格,每次只能向右或者向下走,求从(1,1)走到(n,m)的方案数,其中有些位置是不能走的。

n,m<=1000
2019/7/17 Wednesday 清华大学——赵和旭 34
网格图路径计数
我们从另一个角度来思考这个问题。
我们用搜索算法来计算答案。
然而搜索的时间复杂度是指数级的。
观察一下:这是有些重复计算的。
2019/7/17 Wednesday 清华大学——赵和旭 35
网格图路径计数
我们发现在这个dfs的过程中, dfs出来的值只与带入参数,也就是(x,y)有关,而不同的
(x,y)N*M个,而我们之前搜索的问题在于有大量的重复计算,多次调用同一个
(x,y),每次都从新计算。
有一个很直观的想法就是, 第一次调用的时候就把答案记下来,之后调用不重新算,直接返回之前已经计算出的答案即可。


——这就是记忆化搜索。
2019/7/17 Wednesday 清华大学——赵和旭 36
滑雪
给定一个区域,由一个二维数组给出。数组的(i,j)代表点(i,j)的高度。我们要找一个最长的滑雪路径,注意滑雪只能从高往低处滑。下面是一个例子。


1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
2019/7/17 Wednesday 清华大学——赵和旭 37
解释:一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为

24-17-16-1。当然
25-24-23-...-3-2-1更长。事实上,这是最长的一条。

滑雪
dp[x][y]存储在当前位置下山的最大长度,它等于 它旁边的(上下左右)比它矮的山的
dp值加1 的最大值,即
dp[x][y]=max(dp[x-1][y] , dp[x][y-1] , dp[x][y+1] , dp[x+1][y])+1
要保证对应的高度小于H[x][y]才能取max
1:一般递推式动态规划还要注意枚举状态的顺序,要保证算当前状态时子状态都已经算完了。

2:但是记忆化搜索不需要,因为记忆化搜索就是个搜索,只不过把重复的部分记下来了而已。我们不用像递推一样过于关注顺序,像搜索一样直接要求什么,调用什么就好。


2019/7/17 Wednesday 清华大学——赵和旭 38
滑雪:代码实现
2019/7/17 Wednesday 清华大学——赵和旭 39
dfs函数
主函数部分
记忆化搜索
在有一些dp问题中,状态之间的转移顺序不是那么确定,并不能像一些简单问题一样写几个
for循环就解决了。
我们可以直接计算最终要求的状态,然后在求这个状态的过程中,要调用哪个子状态就直接调用即可,但是每一个状态调用一遍之后就存下来答案,下次计算的时候就直接取答案即可,就不需要从新再计算一遍。


虽然看上去每一次都计算不少,但是因为每一个状态都计算一次,所以均摊下来,复杂度还是状态数
*状态转移。
2019/7/17 Wednesday 清华大学——赵和旭 40
拓扑图DP
2019/7/17Wednesday

清华大学——赵和旭 41

拓扑图的dp
拓扑图dp通常是在拓扑图上求关于所有路径的某种信息之和。当然这里的“和”的运算法则可以是加法或是取
maxmin。或者其他定义的运算。
按拓扑序沿着有向边转移就可以了。
2019/7/17 Wednesday 清华大学——赵和旭 42
BZOJ4562 食物链
给定n个点m条边的有向无环食物网,求其中有多少条极长食物链。
n<=10^5m<=2*10^5
2019/7/17 Wednesday 清华大学——赵和旭 43
BZOJ4562 食物链
拓扑图dp经典题
f[u]为以节点u为终点的食物链数量。
按照拓扑序的顺序转移即可。
2019/7/17 Wednesday 清华大学——赵和旭 44
食物链:代码实现
上面的式子是求一个点时,枚举其所有入边转移,具体写代码的时候,我们一般就只记出边再记录一个入边太麻烦了。所以我们一般不枚举入边,而是枚举出边从每个点往后更新,也就是在上面的式子中,我们对于每个





v向后进行更新,枚举v
出边指向的所有u点,然后f[u]+=f[v]
2019/7/17 Wednesday 清华大学——赵和旭 45
拓扑图dp
其实我们对于一般非有关期望和概率的dp,如果题目中每一个转移关系是双边的,那么如果我们把
dp的每一个状态记为一个点, dp状态之间关系构成的图就是一个拓扑图。

拓扑图dp实际上就是已经给了我们这个拓扑关系了,也就不需要我们自己找了,其实是更简单。

2019/7/17 Wednesday 清华大学——赵和旭 46
背包DP模型
清华大学 赵和旭

背包dp
一般是给出一些“物品,每个物品具有一些价值参数和花费参数,要求在满足花费限制下最大化价值或者方案数。

最简单几种类型以及模型
0/1背包
完全背包
多重背包
2019/7/17 Wednesday 清华大学 赵和旭 48
0/1背包问题
给出n个物品,每个物品有Vi的价值和Wi的费用,我们总共有m块钱,求最多能得到多少价值的物品。

N<=10^3,m<=10^3
2019/7/17 Wednesday 清华大学 赵和旭 49
0/1背包问题代码实现
2019/7/17 Wednesday 清华大学 赵和旭 50
更简便更省空间更常用的写法
0/1背包问题
dp[i][j]表示前i个物品,用了j的体积得到的最大的价值。
dp[i][j]=max{dp[i-1][j] , dp[i-1][j-w[i]]+v[i]}
复杂度O(N^M)
如果要记录方案数呢?
如果要求输出方案呢?
2019/7/17 Wednesday 清华大学 赵和旭 51
完全背包
每一个物品可以选无限个。
dp[i][j]=max{dp[i][j-w[i]],dp[i-1][j]}
在这里提一下一个贪心的预处理
对于所有 Vi≥VjCi≤Cj 的物品 i,都可以完全扔掉
对于体积相同的物品只需要留下价值最大的物品
对于随即数据这个优化的力度非常大。
2019/7/17 Wednesday 清华大学 赵和旭 52
完全背包代码实现
同样也是两种
更简便更省空间更常用的写法
2019/7/17 Wednesday 清华大学 赵和旭 53
多重背包
对每一个物品,最多能用t[i]次。
最暴力的方法就是转移的时候枚举这个物品选几个即可。
dp[i][j] = max{ dp[i-1][j-w[i]*k] + v[i]*k | k<=t[i]}
复杂度O( N*M*t[i] )
2019/7/17 Wednesday 清华大学 赵和旭 54
多重背包O( N*M*t[i] )代码实现
也是差不多的
另一种写法同样简单一些
2019/7/17 Wednesday 清华大学 赵和旭 55
序列上的DP
2019/7/17Wednesday

清华大学——赵和旭 56

区间上的dp状态设计最基本的形式
F[i]表示以i结尾的最优值或方案数。
F[i][k]表示以i结尾附加信息为k的最优值或方案数。
转移的话往往是枚举上一个断点。
F[i]=max { F[j]+ w(j+1,i) | j是一个满足转移条件的断点}
这是最简单的一类序列上的dp
2019/7/17 Wednesday 清华大学——赵和旭 57
bzoj1296 粉刷匠
n条木板要被粉刷,每条木板分为m个格子,每个格子需要被刷成蓝色或红色。

每次粉刷可以在一条木板上给连续的一段格子刷上相同的颜色。每个格子最多被刷一次。

问若只能刷k次,最多正确粉刷多少格子。
n,m<=50k<=2500
2019/7/17 Wednesday 清华大学——赵和旭 58
bzoj1296
如果只有一条木板,那么设g[i][j]表示前i个格子刷j次的最多正确格子
g[i][j]=max{ g[k][j-1]+w(k+1,i) | k<i }
w(x,y)为第x到第y个格子的最多同色格子数,哪个颜色出现的多刷哪个,直接记一个前缀和即可。

有多条木板,设f[i][j]表示前i个木板刷j次的最大答案。
f[i][j]=Max{ f[i-1][k]+gi[m][j-k] | k<=j }
其实像这种一般的dp,就是把影响答案的信息用多维状态来表示,什么必要什么就放在状态里。

2019/7/17 Wednesday 清华大学——赵和旭 59
区间DP
2019/7/17Wednesday

清华大学——赵和旭 60

区间dp状态设计的一般形式
区间dp一般就是设dp[i][j]表示区间[i,j]所能形成的最优答案或者方案数。
或者像序列一样,多加几维表示附加的信息。
2019/7/17 Wednesday 清华大学——赵和旭 61
最简单的区间dp:合并石子
n堆石子,每次只能合并相邻的两堆石子,并且消耗相邻两堆石子个数的体力值,问最少消耗多少体力值将
n堆石子合并为1堆。
N<=100
2019/7/17 Wednesday 清华大学——赵和旭 62
合并石子
dp[i][j]表示将区间[i,j]这段区间内的石子合并为1堆的最小体力值。
答案就是dp[1][n]
转移,考虑对于区间[i,j]它一定是由两段区间[i,k][k+1,j]合并成的,所以转移就考虑枚举
[i,j]区间内的一个分割点k转移即可。
dp[i][j]=min(dp[i][k]+dp[k+1][j] | i<=k<j)+sum[i,j]
2019/7/17 Wednesday 清华大学——赵和旭 63
一定要注意区间dp的枚举顺序!

原文地址:https://www.cnblogs.com/liumengliang/p/11199798.html