1. 程式人生 > >基礎DP總結

基礎DP總結

鏈接 strong else 二分 子序列 邊界 ring sizeof ans

---恢復內容開始---

基礎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個數字能和前面的數字構成的最大和

    可得 : 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(最長遞增子序列)

  分析 :從DP的視角來分析,假設子狀態均已求出,怎麽通過轉移得到答案呢?單單從dp[n-1]轉移得到的答案是正確的嗎?

  

    dp[n]=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(最長公共子序列)

  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

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

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

  題解:

#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;
                else
                    dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]);
        printf("%d\n",dp[len_s1][len_s2]);
    }
}

  註意:邊界處理(i或j為0,1的時候)

三、狀壓DP(二進制枚舉狀態)

稍後補。。。

    

---恢復內容結束---

基礎DP總結