• 线性动态规划


    动态规划入门

    动态规划是一种策略,之前也写过好几篇入门的文章,但都觉得不太深刻,最近做了不少背包dp

    觉得又有了些新的体会,想整理一下。

    动态规划是一种多阶段决策策略,什么是多阶段,就是原问题被划分成了若干个子问题,这些子问题

    的类型与原问题类似,只是规模更小,对于每个子问题的决策叫做多阶段决策。

    动态规划满足最优子结构和无后效性,什么是最优子结构,动态规划的决策一般都依赖于以前已经决策

    好的子问题,这些子问题已经保证了最优解叫做最优子结构,总而保证了这次决策的准确性,无后效性

    指的是,这个子问题的决策与后面的子问题无关系。

    然后就是动态规划最重要最有思维量的两个东西——状态和转移。

    状态就是每个子问题的条件,我们不仿把动态规划看作一个填表的过程,试想一下,这个题目中有什么

    条件会影响我们的决策,状态的设计要求全面描述和简洁。

    转移是我认为动态规划中最难的东西,就是设计一个方程,后面的状态怎么通过前面的最优子结构算出

    自己的值。


    如果上面比较混乱qwq,你或许可以看看下面的板正版的

    一,概念篇

    1,动态规划:通过计算出小问题的最优解,可以推出大问题的最优解,从而可以推出更大问题的最优解,最小问题即是边界情况。

    2,子问题(小问题):子问题是一个与原问题有着类似的结构,但规模比原问题小的问题。

    3,最优子结构:动态规划的问题一般是求解全局最优解,而全局最优解是由局部的最有解一步一步推出,局部的最优解称为最优子结构。

    4,动态规划的基本思想:将待求解的问题划分为若干个阶段(子问题),按顺序求解子问题,子问题的求解为更大子问题的求解提供信息,由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次。

    动态规划的核心思想也有减少冗余,对于会发生重叠的子问题只计算一次,去除冗余。

    5,状态表示和最优化值。

    状态表示是对当前子问题的解的局面的(条件)一种全面的描述。

    最优化值是该状态表示下的最优化值(方案值),我们最终能通过其直接或间接得到答案。

    6,状态的设计

    具有最优化总结构,能够全面描述某一个局面,尽量简洁。

    设计状态的关键是充分描述,尽量简洁。

    7,动态规划的精髓——状态转移:通过已知的较小问题的最优值得出较大问题的最有值的过程。状态的转移需要满足要考虑到所有的可能性。状态转移的实质是一个DAG,可以把状态抽象成点,转移抽象成边,转移就是从子问题指向当前状态。

    8,无后效性:因为动态规划的转移过程是一个DAG,所以保证了当前状态仅由之前的子问题转移而来,而与后面的状态没有什么关系。

    9,动态规划的时间复杂度估计

    O=状态数*状态转移的复杂度


    动态规划一般由记忆化搜索或者数组递推实现。

    动态规划常用于解决计数问题(求方案数的问题)和最有值问题(最大价值,最小花费)


    入门例题不想过多的去讲,就阐述一道最经典的数字三角形来模拟一下动态规划的思路

    观察下面的数字金字塔。

    写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

             7

          3   8

        8   1   0

      2   7   4   4

    4   5   2   6   5 

    显然这是一个最有值问题,首先适用于动态规划求解。

    状态:这个题只有位置需要记录,位置会影响我们的决策,很显然。

    状态转移:显然要从下面到顶部的最大值,肯定要求从这个点左下方来的路径与右下方来的路径哪个和大要哪个,下面的每个点

    都是通过这个策略求出的,所以满足最优子结构。


     

    线性动态规划经典——最长上升子序列(LIS)

    顾名思义,最长上升子序列就整个序列满足单调递增性质的子序列中长度最长的一个。

    比如 序列 5,3,2,6,7,

    其中上升的子序列有

    5; 3; 2; 6; 7; 2,6;2,6,7; 6,7;3,6;3,7; 3,6,7;5,6;5,6,7;5,7;

    显然其中最长的子序列是2,6,7或3,6,7或5,6,7。最长上升子序列的长度为3。

    如何用动态规划的方法求解最长上升子序列的长度?

    方法很简单,我们设一个数组dp[i]表示以a[i]为结尾的最长上升自序列,最后所有dp[i]中最大的那个就是这个序列的LIS。

    求解dp数组的方法:

    显然,我们为了让序列的长度更长,所以一定要把a[i]接在它能接在的dp[j]最大的a[j]后面,因此我们就得到了求解dp数组的状态转移方程:

    dp[i]=max{ dp[j] | a[j]<a[i] && j<i } +1 ;

    因此我们就得到了一个O(n^2 )的做法:

    1 for(int i=1;i<=n;i++)
    2 
    3 {
    4 
    5 dp[i]=1;
    6 
    7 for(int j=1;j<i;j++)
    8 
    9     if(a[i]>a[j]&&dp[j]+1>dp[i]) dp[i]=dp[j]+1; }

    若数据范围较大,显然O(n^2 )的做法恐怕不行,那么我们是否可以优化一下这个做法,dalao zhaohx讲动态规划的优化分为两种第一种是减少状态量,、

    第二种是加快转移过程。加快转移过程又分为性质优化和数据结构优化。LIS可以采用性质优化和数据结构优化两种。

     

    1,性质优化 O(nlogn)

    我们仔细观察之前的那个状态转移方程dp[i]=max{ dp[j] | a[j]<a[i] && j<i } +1 ;

    我们会发现这个状态转移方程的作用,它每次寻找的是小于a[i]的a[j]中dp[j]最大的那一个,此时我们可以采用一个辅助数组h[k]表示,

    我们设h[k]表示dp[j]==k的所有j当中的最小的a[j],就是说长度为k的最长上升序列,最后一个元素的最小值是多少,因为最后一个元素越小,

    肯定后面更容易再加上一个元素了。

    因此我们发现了一个性质,h[k]一定是单调递增的,当我们新加入一个元素的时候,如果这个元素比末尾元素大,那么这个元素就可以接在末尾

    之后即h[++k]=a[i];如果这个元素小于末尾的元素,我们就要给这个元素找一个位置,位置满足的性质是这个元素a[i]能接在某个h[k]之后,且这

    个元素要小于h[k+1],因为这样后面的元素才更容易接上,这里我们运用到了二分查找来修改。

     

    算法流程:

    如果这个元素大于d[len],直接让d[len+1]=a[i],然后len++。这个很好理解,当前最长的长度变成了len+1,而且d数组也添加了一个元素。

    如果这个元素等于d[len],那么可以保证d[1..len-1]都是小于a[i]的(根据上面的证明),因此这个元素就没有什么意义了,直接忽略就好,

    因为它无法接在任何一个元素d后面产生一个更有优势的子序列。

    如果这个元素小于d[len],那么就在d数组中找到第一个大于等于它的元素(这个元素必然存在,至少d[len]就是),把这个元素替换成a[i]即可。

    它必定可以替换掉一个比它大的但在同一h[k]的元素,比如最特殊的情况它会替换掉h[k]。

     1 for(int i=1;i<=n;i++)
     2 {
     3     if(num[i]>dp[len]) dp[++len]=num[i];
     4     else if(num[i]<dp[len])
     5     {
     6         int head=1,tail=len;
     7         while(head<=tail)
     8         {
     9             int mid=(head+tail)>>1;
    10             if(dp[mid]>=num[i]) tail=mid;
    11             else head=mid+1;
    12         }
    13         dp[head]=num[i];
    14     }
    15 }

    2,树状数组优化,表示并不会

    对类似问题的探讨

    类似的还有最长不下降子序列,最长下降子序列以及最长上升子序列。

    注意最长不下降子序列和最长上升子序列不同,最长不下降子序列左右元素可以相等。


     

    线性动态规划经典——最长公共子序列

    最长公共子序列:子序列与子串不同,可以不连续。

    我们可以类比最长上升子序列,设dp[x][y]表示第一个串的前x位与第二个串的前y位的最长公共子序列。

    考虑三种情况

    1,如果s1[x]不在公共子序列中,那么dp[x][y]=dp[x-1][y];

    2,如果s2[y]不在公共子序列中,那么dp[x][y]=dp[x][y-1];

    3,如果s1[x]==s2[y],dp[x][y]=dp[x-1][y-1]+1;

    综上状态转移方程为三者的最大值。

     

     1 inline void lcs(char *s1,char *s2)
     2 {
     3     int s1len=strlen(s1+1);
     4     int s2len=strlen(s2+1);
     5     for(int i=1;i<=s1len;i++)
     6         for(int j=1;j<=s2len;j++)
     7         {
     8             dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
     9             if(s1[i]==s2[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
    10         }
    11 }

     

     

      

     

     

     

  • 相关阅读:
    Python List+Tuple+Dict+Set小结
    小命要紧~风热风寒感冒
    IDEA忽略编译报错
    IDEA新建一个Spring Boot项目
    ip2region.jar实现ip转地址
    IDEA控制台中文乱码问题
    IDEA报错Plugin "XXX" was not loaded: required plugin "Java EE: EJB, JPA, Servlets" is disabled.
    IDEA Error:java: Compilation failed: internal java compiler error
    Failed to start component [StandardEngine[Catalina].StandardHost[localhost]
    浏览网页隐藏服务器IP
  • 原文地址:https://www.cnblogs.com/Hoyoak/p/11387602.html
Copyright © 2020-2023  润新知