zoukankan      html  css  js  c++  java
  • 基础DP总结

    基础DP总结

    关键词:基础DP问题,LIS,LCS,状压DP

    简析 :DP大法好啊,当一个大问题不好解决的时候,我们研究它与其子问题的联系,然后子问题又找它的子问题,如此下去,一直推,一直减小到可以轻而易举求出答案(称为边界)。所以解决DP问题就是要推出一个正确的递推式。

    一、DP解决基础递推问题

      1)斐波那契数列

        dp[n] = dp[n-1] + dp[n-2]

        边界:dp[0] = 0 , dp[1] = 1 .

      2 )Max sum

        题目链接 : http://acm.hdu.edu.cn/showproblem.php?pid=1003

        dp[n]表示从1~n段的最大子串和

        用sum存储第n个数字能和前面的数字构成的最大和,而sum也在递推的过程中易求

        可得 : dp[n] = max( dp[n-1] , sum )

            边界:求出dp[1]

        只有一个状态,一个for循环即可,O(N).

      3)经典DP问题:数塔

        题目链接 : http://acm.hdu.edu.cn/showproblem.php?pid=2084

        分析 : 数塔就像一个满二叉树啊。我到底是走左边还是走右边能获得最大值呢?然后每一个点都能这样考虑,就是一个递归的问题了,普通的递归必然      时间复杂度过大,那再记忆化递归,求完每点的答案用一个数组存储,那就可以了。其实这已经是DP的解法了,下面来看看标准的DP解法

        dp[i][j] : 储存从高度为i,左边数第j个节点走到底的数字和最大值

        dp[i][j] = max( dp[i-1][j]  , dp[i-1][j+1] )

        边界:求出底层 dp[0][j] 的值

        代码参考 : 题目链接里的discuss

    二、LIS,LCS问题

      仅作简要分析

      1)LIS(最长递增子序列)

      题目链接 : http://poj.org/problem?id=2533

      分析 :从DP的视角来分析,设dp[n-1]表示前面n-1个数字的最长递增子序列长度,在求dp[n]时多了一个数字(第n个数字),判断这个数字能够与前面的数字构成大于dp[n-1]的序列不。。。。然后发现其实还不如直接设dp[n]为以第n个数字为最大值的序列的长度,这样后面的数字如果比这个数字大,就加一。再得到最终结果就是dp数组中的最大值。

      

        dp[n]=1;
    for(i = 0; i<n;i++)
       {  
            if(a[n]>a[i])
              dp[n]=max(dp[n],dp[i]+1);        
        }    
    

        时间复杂度:O(N*N)

      然而这个复杂度在ACM中是过不了题的,对其优化就在于减少子状态的数量,子状态中两个长度相同的递增序列,那么保留最大值小的不就行了。然后发现还可以二分查找,让复杂度降到了O(N*logN)。

    int ans[MAX_N], a[MAX_N], dp[MAX_N], n;  // ans 用来保存每个 dp 值对应的最小值,a 是原数组
    int len; // LIS 最大值
    
    ans[1] = a[1];
    len = 1;
    
    for (int i = 2; i <= n; ++i) {
        if (a[i] > ans[len]) {	//改成>=的话,下面
            ans[++len] = a[i];
        } else {
            int pos = lower_bound(ans, ans + len, a[i]) - ans;  //改成upper_bound
            ans[pos] = a[i];
        }
    }
    
    cout << len << endl;  // len 就是最终结果
    

      2)LCS(最长公共子序列)

      题目链接 : http://acm.hdu.edu.cn/showproblem.php?pid=1159

      dp[i][j]: A串0 ~ i 与B串0 ~ j 的最长公共子序列

      DP分析:还是当dp[i][j]之前的状态已知,怎么推出dp[i][j]

     

              I :dp[i][j] = max( dp[i-1][j] , dp[i][j-1] );

            II : 如果字符 i 刚好和字符 j 匹配,则dp[i][j] = dp[i-1][j-1] + 1

          想不到还有其它的可能了。

      题解:

     

    #include<stdio.h>
    #include<string.h>
    
    char s1[1005], s2[1005];
    int dp[1005][1005];
    int max(int x,int y)
    {
    	if(x>y) return x;
    	else return y;
    } 
    int main(){
        int len_s1,len_s2,i,j;
    	while(~scanf("%s%s", s1, s2)){
             len_s1 = strlen(s1), len_s2 = strlen(s2);
            memset(dp, 0, sizeof(dp));   
            for( i = 0; i < len_s1; i++)
                for( j = 0; j < len_s2; j++)
                    if(s1[i] == s2[j])
                        dp[i + 1][j + 1] = dp[i][j] + 1;    //这个必不会小于下面的max,所以不用再比较了
                    else
                        dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]);
            printf("%d
    ",dp[len_s1][len_s2]);
        }
    }
    

     

      注意:边界处理(i或j为0,1的时候)

    三、状压DP(二进制枚举状态)

       

      简析 :当元素的状态非常多并且与“取与不取”有关时,我们可以用二进制来压缩状态。举个栗子,用1表示取,用0表示不取,对某行的5个元素全取,它的状态表示为11111,全不取,则为00000,对应的十进制数分别为31,0.于是这两个状态可以表示为dp[i][31],dp[i][0]。

      题目链接 : http://poj.org/problem?id=3254

      分析 : 第i行的方案必定与第i-1行有关,则可以拿其每个状态都去遍历i-1的状态,然后总方案数就是每个状态的方案数之和。

      代码 : 见discuss (哈哈,要去赶实验报告,有空再补上)

  • 相关阅读:
    Linux下查看CPU型号,内存大小,硬盘空间的命令
    java_opts 参数与JVM内存调优
    less 查看日志
    如何实时查看Linux下日志
    mysql的sql语句的性能诊断分析
    使用zabbix-java-gateway可以通过该网关来监听多个JVM
    性能瓶颈分析
    Git客户端的安装与配置入门
    渗透测试的8个步骤 展现一次完整的渗透测试过程及思路
    Python&selenium&tesseract自动化测试随机码、验证码(Captcha)的OCR识别解决方案参考
  • 原文地址:https://www.cnblogs.com/lnu161403214/p/7821417.html
Copyright © 2011-2022 走看看