0 1 1 2 3 5 8 12 ...
- 递归(重复子问题,TIL) -> 递推(空间换时间 + 记录旧纪录)
- 记忆化搜索(用数组等将已经算过的东西记录下来,在下一次要使用的直接用已经算出的值,避免重复运算,去掉的重复的搜索树)
  • todo 引入2:走楼梯问题
      有一人要爬n阶的楼梯,他一次可以爬1阶或2阶,问要爬完这n阶楼梯,共有多少种方法?
- 根据分类加法原理:
  - 第n阶的方法 = n-1阶的方法 + n-2阶的方法
- 同样的,对于n-1阶和n-2阶我们也可以用类似的方法进行求解
        f[1] = 1, f[2] = 2
        f[i] = f[i-1] + f[i-2]
  • 例1:数字三角形(poj1163)
      设有一个三角形的数塔,顶点结点称为根结点,每个结点有一个整数数值。从顶点出发,可以向左走,也可以向右走。如图所示。
      问题:当三角形数塔给出之后,求从第一层到达底层的路径最大值。
- $$f[i,j]=max(f[i-1,j],f[i-1,j-1])+a[i,j];$$
  • 基本原理
    • 分类加法原理:
      • 做一件事,完成它可以有n类办法,在第一类办法中有m1种不同的方法,在第二类办法中有m2种不同的方法,……,在第n类办法中有mn种不同的方法,那么完成这件事共有N=m1+m2+m3+…+mn种不同方法。
    • 分步乘法原理:
      • 做一件事,完成它需要分成n个步骤,做第一步有m1种不同的方法,做第二步有m2种不同的方法,……,做第n步有mn种不同的方法,那么完成这件事共有N=m1×m2×m3×…×mn种不同的方法。
  • Concepts
    • 动态规划 description: 解决多阶段决策过程最优化问题的一种方法。
    • 阶段 description: 把问题分成几个相互联系的有顺序的几个环节,这些环节即称为阶段。
    • 状态 description: 某一阶段的出发位置称为状态。通常一个阶段包含若干状态。
    • 决策 description: 从某阶段的一个状态演变到下一个阶段某状态的选择。
    • 策略 description: 由开始到终点的全过程中,由每段决策组成的决策序列称为全过程策略,简称策略。
    • 状态转移方程 description: 前一阶段的终点就是后一阶段的起点,前一阶段的决策选择导出了后一阶段的状态,这种关系描述了由 i 阶段到 i+1 阶段状态的演变规律,称为状态转移方程。
  • 动态规划适用的基本条件
    • 具有相同子问题
      • 保证这个问题能够分解出几个子问题,并且能够通过这些子问题来解决这个问题。
      • 将这些子问题做为一个新的问题,它也能分解成为相同的子问题进行求解。
        • 假设我们一个问题被分解为了A,B,C三个部分,那么这A,B,C分别也能被分解为A,B,C三个部分,而不能是D,E,F三个部分。
      • 问题的最优解包含着它的子问题的最优解
        • 不管前面的策略如何,此后的决策必须是基于当前状态(由上一次决策产生)的最优决策
      • 反例:
        • image.png
        • 在上图中找出从第1点到第4点的一条路径,要求路径长度mod 4的余数最小。
    • 无后效性
      • “过去的步骤只能通过当前状态影响未来的发展,当前的状态是历史的总结”。
        • 这条特征说明动态规划只适用于解决当前决策与过去状态无关的问题
      • 状态,出现在策略任何一个位置,它的地位相同,都可实施同样策略,这就是无后效性的内涵
      • 这是动态规划中极为重要的一点,如果当前问题的具体决策,会对解决其它未来的问题产生影响,如果产生影响,就无法保证决策的最优性。
  • Solution
    • 结合原问题和子问题确定状态
      • (一维描述不完就二维,二维不行就三维四维……总之要敢想)
      • 状态的参数一般有
        • 描述位置的:前(后)i单位,第i到第j单位,坐标为(i,j)等
        • 描述数量的:取i个,不超过i个,至少i个等
        • 描述对后有影响的:状态压缩的,一些特殊的性质
    • 确定转移方程
      • 检查参数是否足够;
      • 分情况:最后一次操作的方式,取不取,怎么样放,前一项是什么
      • 初始边界是什么。
      • 注意无后效性。
        • 比如说,求A就要求B,求B就要求C,而求C就要求A,这就不符合无后效性了。
        • 根据状态枚举最后一次决策(即当前状态怎么来的)就可确定出状态转移方程!
    • 考虑需不需优化
    • 确定编程实现方式 (自顶向下 / 自下而上)
      • 递推
      • 记忆化搜索
  • todo 例2:路径条数问题 unique-paths
      N * M 的棋盘上左上角有一个过河卒,需要走到右下角。卒行走的规则:可以向下、或者向右。现在要求你计算出卒从左上角能够到达右下角的路径的条数,
- Solutions
  - 确定状态——原问题是啥?子问题是啥?
    - 从(0,0)走到(n,m)的路径数
    - 从(0,0)走到(i,j)的路径数
    - f[i][j]表示从左上角走到 (i,j)点的路径条数,
  - 确定状态转移方程和边界
    - $$f[i][j] = f[i-1][j] + f[i][j-1]$$
    - $$f[1][1] = 0;$$
  - 考虑是否需要优化
  - 确定实现方法
- [ ] #todo 扩展2.1:过河卒 
        棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
        棋盘用坐标表示,A点(0, 0)、B点(n, m)(n, m为不超过20的整数),同样马的位置坐标是需要给出的。
        现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
- [ ] #todo 扩展2.2:传纸条 
        小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
        在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
        还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度只和最大。现在,请你帮助小渊和小轩找到这样的两条路径。
  • 例3:传球游戏
      n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止。
      聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。
      两种传球的方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方式有1->2->3->1和1->3->2->1,共2种。
- Solutions
  - 确定状态——原问题是啥?子问题是啥?
    - 原问题:从1开始传球第m步球回到1的方法数
    - 子问题:从1开始传球第i步球到达j的方法数
      - f[i][j]表示第i次传球之后球在第j个人手上的方法数
  - 确定状态转移方程和边界
    - $$f[i][j] = f[i-1][j-1] + f[i-1][j+1]$$
      $$f[0][1] = 1;$$
    - 注意由于是一个环,j=1 时 左边(j-1) 为n,j=n 时 右边(j+1)为1
  - 考虑是否需要优化
  - 确定实现方法
  • 例4:过河
      在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中 L 是桥的长度)。青蛙从桥的起点0开始,不停的向终点L方向跳跃。一次跳跃的距离是 S 到 T 之间的任意正整数(包括 S,T)。当青蛙跳到或跳过坐标为 L 的点时,就算青蛙已经跳出了独木桥。 题目给出独木桥的长度 L,青蛙跳跃的距离范围 S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。 M<= 100; S,T<=10, L<=10^9
- 第一步:确定状态——原问题是啥?子问题是啥?
  原问题:从0开始跳到>=L的位置最少踩到的石头数
  子问题:从0开始跳到i位置最少踩到的石头数
  f[i]为从0跳到i的最少踩到的石头数
  第二步:确定状态转移方程和边界
   f[i]=min(f[i-k] + a[i]) (S≤k≤T) (a[i] 为i点的石子数)
  第三步:考虑是否需要优化
  时间和空间都不允许……
- 再次审视数据范围
  M<= 100; S,T<=10, L<=10^9
  M很小——石头很稀疏
  S,T也不大——S与T不等的时候,超过一定范围的所有距离都是可以跳到的
  这个一定范围是多少??
  S= 9 ,T=10 时 >=90的距离都可以表示
  (S和T相等的时候只需要看S的倍数有多少石头)
  • 例5:滑雪(poj1088)
      Michael喜欢滑雪,这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长底滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子 
      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
      一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。
- 第一步:确定状态——原问题是啥?子问题是啥?
  f[i][j]表示从(i,j)滑下的最长路径长度
  第二步:确定状态转移方程
  ——由于f[i][j]由上下左右四个方向转移过来
  F[i][j] = max {f[i  - 1][j] + 1    (a[i - 1][j] <a[i][j])
                 {f[i + 1][j] + 1    (a[i + 1][j] <a[i][j])
                 {f[i][j - 1] + 1     (a[i][j - 1] <a[i][j])
                 {f[i][j + 1] + 1    (a[i][j + 1] <a[i][j])
      (初值):f[i][j] = 0
  第三步:考虑是否需要优化
  第四步:确定实现方法
  相信同学们已经看出问题了,我们没法通过递推的方式在算f[i][j]之前将他上下左右四个点的值都求出来!!!
      设有一个正整数的序列:b1,b2,…,bn,对于下标i1<i2<…<im,若有bi1≤bi2≤…≤bim
      则称存在一个长度为m的不下降序列。
        例如,下列数列
           13  7  9  16  38  24  37  18  44  19  21  22  63  15
        对于下标i1=1,i2=4,i3=5,i4=9,i5=13,满足13<16<38<44<63,则存在长度为5的不下降序列。
        但是,我们看到还存在其他的不下降序列: i1=2,i2=3,i3=4,i4=8,i5=10,i6=11,i7=12,i8=13,满足:7<9<16<18
      <19<21<22<63,则存在长度为8的不下降序列。
        问题为:当b1,b2,…,bn给出之后,求出最长的不下降序列。
- 第一步:确定状态——原问题是啥?子问题是啥?
  F[i] 前i个数的最长不下降子序列——求不了啊~!为什么求不了?
  不知道这个序列的最后一个元素是哪个,没法转移
  F[i]以第i个数为结尾的最长不下降子序列
  第二步:确定状态转移方程
  f[i]=max{f[j]+1}(a[j]<=a[i] 且 j<i)
  f[i] = 1
- f[i] = 1
  f[i]=max{f[j]+1}(a[j]<=a[i] 且 j<i)
- 第三步:考虑是否需要优化
  O(N^2)
  可以使用单调队列或者线段树等数据结构优化到O(NlogN) ——留给学有余力的同学
  第四步:确定实现方法
- 拓展6.1:拦截导弹
        某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
        输出这套系统最多能拦截的导弹数和要拦截所有导弹最少要配备这种导弹拦截系统的套数。
  - 认真分析一下题就会发现:每一个导弹最终的结果都是要被打的,如果它后面有一个比它高的导弹,那打它的这个装置无论如何也不能打那个导弹了,也就是说最少要打最长上升子序列长度那么多次可以打完!
  - **完整覆盖一个序列的不上升子序列的个数等于这个序列的最长上升子序列的长度,**严格证明数学请参考数学中偏序集的Dilworth定理。
  • 例7:最大子串和
      题意:给你一个有正有负的序列,求一个=子串(连续的一段),使其和最大!
      样例输入: -5 6 -1 5 4 -7
      样例输出: 14
- 第一步:确定状态——原问题是啥?子问题是啥?
  f[i]前i个数的最大子串和——出现了和前一个题一样的问题
  用f[i]表示一定要选第i个数的情况下前i个数的最大子串和(选第i个数和其左边连续的若干个)
  第二步:确定状态转移方程
  f[i] =max(f[i - 1]+a[i], a[i])
  第三步:考虑是否需要优化
  O(N)
  第四步:确定实现方法
- 一个问题:
  如果我一定要你求前i个数的最大子序列和怎么办?
- g[i] = max (f[i], g[i-1])
- 扩展7.1:不相交的两个子串的最大和
        给你一个有正有负的序列,求两个不重叠的子串,使其和最大!
         f[i][j] 前i个数中间挑出j个子串的最大和
        f[i][j] = max(f[i-1][j] + a[i], f[k][j-1]+a[i])   0<k<i
        O(N^2)
  - 其实只有两个子串,为什么不枚举两个子串的分界点呢?
    假如 右边那个串是从k位置开始的
    我们是不是可以先求一个F’[i] 表示以i为子串最左边那个数的最大子串和
    再求一个G[i]表示前i个数的最大子串和
    对每个k 都计算一下F’[k]+G[k]最后取一个最大值就可以了
- 扩展7.2:不相交的m个子串的最大和
        给你一个有正有负的序列,求m个不重叠的子串,使其和最大!
         f[i][j] 前i个数中间挑出j个子串的最大和
        f[i][j] = max(f[i-1][j] + a[i], f[k][j-1]+a[i])   0<k<I
        O(M*N^2)
- 扩展7.3:最大子矩形
        在一个矩阵中找到一个子矩阵,该子矩阵和最大!!输出最大和即可。
        0  -2  -7  0 
        9   2  -6  2 
        -4  1  -4  1 
        -1  8  0  -2 
- 枚举行(子矩形从第i行到第j行,i和j都要枚举)
  然后把第i行到第j行压成一行
  然后求最大子串和
  • 例8:最长公共子序列
      给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
      最长公共子序列:公共子序列中长度最长的子序列。
      给定两个序列X={x1,x2,…,xm}和Y={y1,y2,…, yn},找出X和Y的一个最长公共子序列。
      样例输入:abcfbc
               abfcab
      样例输出:4
- 第一步:确定状态——原问题是啥?子问题是啥?
  f[i][j]表示前一个字符串的前i位与后一个字符串的前j位的最长公共子序列长度
  第二步:确定状态转移方程
  当x[i]==y[j],f[i][j]=f[i-1][j-1]+1
  当x[i]!=x[j],f[i][j]=max(f[i - 1][j] ,f[i][j - 1])
  a[1]==b[1] f[1][1] = 1
  else f[1][1] = 0
  第三步:考虑是否需要优化
  第四步:确定实现方法
- 拓展8.1:回文词
         回文词是一种对称的字符串——也就是说,一个回文词,从左到右读和从右到左读得到的结果是一样的。任意给定一个字符串,通过插入若干字符,都可以变成一个回文词。你的任务是写一个程序,求出将给定字符串变成回文词所需插入的最少字符数。比如字符串“Ab3bd”,在插入两个字符后可以变成一个回文词(“dAb3bAd”或“Adb3bdA”)。然而,插入两个以下的字符无法使它变成一个回文词。
        给出一个字符串求出使其变为回文串需要插入的最少字符数。
  - 分析:假定我们已经先原串中找到了一个最长回文串,然后对于原串中不属于这个回文串的字符,在它关于回文串中心的对称位置添加一个相同字符。那么需要添加的字符数量即为 :n-最长回文串长度。
    最长回文串对称轴前的每一个字符即原串当中也都能与对称轴后的字符一一对应。如果我们将原串进行翻转,最长回文串中的字符必然会在原串和逆序串中也一一对应,所以可以通过求原串和逆序串的最长公共子序列得到最长回文串的长度!
- 拓展8.2:最长公共子序列
  - 输出最长公共子序列的方案
  • 例9:乘积最大
      设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积能够为最大。
      如:有一个数字串:312, 当N=3,K=1时会有以下两种分法:1) 3*12=362) 31*2=62
      这时,符合题目要求的结果是:31*2=62
- 第一步:确定状态——原问题是啥?子问题是啥?
  f[i][j]代表前i位数中间添加J个乘号所得到的的最大乘积
  第二步:确定状态转移方程
   f[i][j] = max{f[k][j - 1] + num[k + 1][i]} (1 <= k < i)
  num[i][j]代表第i位数到第j位数组成的数字是多少。
  第三步:考虑是否需要优化
  第四步:确定实现方法
-