• 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的枚举顺序!

  • 相关阅读:
    Python rindex()方法
    Python rfind()方法
    Python replace()方法
    服务器技术综述(三)
    服务器技术综述(二)
    服务器技术综述(一)
    TensorRT-优化-原理
    TensorRT-安装-使用
    TensorRT 加速性能分析
    GPU加速:宽深度推理
  • 原文地址:https://www.cnblogs.com/liumengliang/p/11199798.html
Copyright © 2020-2023  润新知