1. 程式人生 > >演算法(一) --DP動態規劃(LIS和LCS)

演算法(一) --DP動態規劃(LIS和LCS)

1.http://blog.csdn.net/u013445530/article/details/45645307

DP問題是ACM裡面最難的,因為太考思維能力了,只有將狀態轉移方程推出來才能解決問題,DP問題也是面試的時候最容易考到的,希望大家好好學DP,至少在面試的時候不吃虧。

  1. 第一個問題
d(i)=min{ d(i-vj)+1 }   狀態轉移方程 其中i-vj >=0vj表示第j個硬幣的面值;
d(i)=j來表示湊夠i元最少需要j個硬幣
  1. int main()      //程式碼求解的是一個數值 最少由多少個硬幣組成。
  2. {  
  3.     int a[3] = {1,3,5},sum = 11,cent = 0,dp[12];  
  4.     dp[0] = 0;  
  5.     for(int i = 1; i <= sum; i++) dp[i] = i;//我們假設存在1元的硬幣那麼i元最多隻需要i枚1元硬幣,當然最好設定dp[i]等於無窮大
  6.                                             //dp[i]=i 是代表 最多的話需要i個一元硬幣
  7.     for(int i = 1; i <= sum; i++){      / /i代表數值
  8.         for(int j = 0; j < 3; j++){     //j代表總共的硬幣種類數目    i代表要求和的數
  9.             if
    (i >= a[j] && dp[i - a[j]] + 1 < dp[i]){     //如果i的數值大於A【j】裡面的數 (即數值大於硬幣代表的數)
  10.                 dp[i] = dp[i- a[j] ] + 1;                //注意這個是從小的數值往大的數值上 增加。 
  11.             }                //所以 如果i的數值大於A【j】裡面的數 以及dp[i - a[j]] + 1   選個硬幣值a【j】所以要加1 
  12.         }  
  13.     }  
  14.     cout<<dp[sum]<<endl;  
  15.     return 0;  
  16. }  

第二個問題

將一個由N行數字組成的三角形,如圖所以,設計一個演算法,計算出三角形的由頂至底的一條路徑,使該路徑經過的數字總和最大。

好了,現在我們用DP解決這道問題

將上圖轉化一下:


假設上圖用map[][]陣列儲存。

f[i][j]表示從頂點(1, 1)到頂點(i, j)的最大值。

則可以得到狀態轉移方程:

f[i][j] = max(f[i+1][j], f[i+1][j+1]) + map[i][j]

此題既適合自頂而下的方法做,也適合自底而上的方法,

當用自頂而下的方法做時,最後要在在最後一列數中找出最大值,

而用自底而上的方法做時,f[1][1]即為最大值。

所以我們將圖2根據狀態轉移方程可以得到圖3

    這裡用的是從底端向上計算

最大值是30.

程式碼如下:

  1. 1 #include <cstdio>    
  2. 2. #include <iostream>    
  3. 3. #include <algorithm>    
  4. 4. #include <cstring>    
  5. 5. usingnamespace std;    
  6. 6. int a[2000][2000];    
  7. 7. int main()    
  8. 8. {    
  9. 9.     int t,n,i,j;    
  10. 10.     while(~scanf("%d",&n))         
  11. 11.     {   
  12. 12.         for(i=0; i<n; i++)    
  13. 13.             for(j=0; j<=i; j++)    
  14. 14.                 scanf("%d",&a[i][j]);       //先把數存進去
  15. 15.         for(i=n-1; i>0; i--)        //因為是從底部往上面求 所以i從n-1 開始  從倒數第二行開始算起
  16. 16.             for(j=0; j<i; j++)       //注意這裡j的範圍  j從每行的第一個數算起  算到每行最後一個數,最後一個數比i小1
  17. 17.                 a[i-1][j]+=max(a[i][j],a[i][j+1]);     // 讓此位置的值加上 下面兩個最大的值 即為最大
  18. 18.         printf("%d\n",a[0][0]);    
  19. 19.     }    
  20. 20.     return 0;    
  21. 21. }    

問題三

一個序列有N個數:A[1],A[2],…,A[N],求出最長非降子序列的長度。 (DP基本都會講到的一個問題LISlongest increasing subsequence)

正如上面我們講的,面對這樣一個問題,我們首先要定義一個狀態來代表它的子問題,並且找到它的解。注意,大部分情況下,某個狀態只與它前面出現的狀態有關,而獨立於後面的狀態。

讓我們沿用入門一節裡那道簡單題的思路來一步步找到狀態狀態轉移方程。假如我們考慮求A[1],A[2],…,A[i]的最長非降子序列的長度,其中i<N,那麼上面的問題變成了原問題的一個子問題(問題規模變小了,你可以讓i=1,2,3等來分析然後我們定義d(i),表示前i個數中以A[i]結尾的最長非降子序列的長度。OK,對照入門中的簡單題,你應該可以估計到這個d(i)就是我們要找的狀態。如果我們把d(1)d(N)都計算出來,那麼最終我們要找的答案就是這裡面最大的那個。狀態找到了,下一步找出狀態轉移方程。

為了方便理解我們是如何找到狀態轉移方程的,我先把下面的例子提到前面來講。如果我們要求的這N個數的序列是:

534867

根據上面找到的狀態,我們可以得到:(下文的最長非降子序列都用LIS表示)

· 1個數的LIS長度d(1)=1(序列:5)

· 2個數的LIS長度d(2)=1(序列:33前面沒有比3小的)

· 3個數的LIS長度d(3)=2(序列:344前面有個比它小的3,所以d(3)=d(2)+1)

· 4個數的LIS長度d(4)=3(序列:3488前面比它小的有3個數,所以 d(4)=max{d(1),d(2),d(3)}+1=3)

OK,分析到這,我覺得狀態轉移方程已經很明顯了,如果我們已經求出了d(1)d(i-1),那麼d(i)可以用下面的狀態轉移方程得到:

d(i) = max{1, d(j)+1},其中j<i,A[j]<=A[i]

用大白話解釋就是,想要求d(i),就把i前面的各個子序列中,最後一個數不大於A[i]的序列長度加1,然後取出最大的長度即為d(i)。當然了,有可能i前面的各個子序列中最後一個數都大於A[i],那麼d(i)=1,即它自身成為一個長度為1的子序列。

分析完了,上圖:(第二列表示前i個數中LIS的長度,第三列表示,LIS中到達當前這個數的上一個數的下標,根據這個可以求出LIS序列)

 

程式碼:

1. #include <cstdio>    

2. #include <iostream>    
3. #include <algorithm>    
4. #include <cstring>    
5. usingnamespace std;    
6.      
7. int main()    //最長非降子序列的長度
8. {    
9.     int dp[2000],a[2000],n;    
10.     while(cin>>n)    
11.     {    
12.         memset(dp,0,sizeof(dp));    
13.         int res = 0;    
14.         for(inti = 0; i < n; i++) cin>>a[i];    //先存入序列
15.      
16.         for(int i = 0; i < n; i++)    
17.         {                                                  //迴圈是從第一個數值開始往後求 dp[i] 
18.             dp[i] = 1;              // 先假設所有的 最長為1    即為其本身  eg:用3 5 4 去想一遍流程
19.             for(int j = 0; j < i; j++)    //j小於i   j為i前面的一個數據
20.             {    
21.                 if(a[j] < a[i])  //因為求的是最長非降子序列 LIS,即序列往後只能增加數  

                                                            //這裡每次都從0開始 遍歷   保證dp[i]為最長的
22.                 dp[i] = max(dp[i],dp[j] + 1); //   求出的序列 前面的數只能小於或者等於後面的數
23.             }    
24.      
25.           res = max(res,dp[i]);      //res存放每次 算出來的最大 dp[i]
26.         }    
27.      
28.         cout<<res<<endl;    
29.     }    
30.     return0;    
31. }  


問題四

最長上升公共子序列問題: LCS  (最長公共子序列) 只要兩個字串裡面都有 即可。數字不必相鄰

問題描述

    什麼是最長公共子序列呢?好比一個數列S,如果分別是兩個或多個已知數列的子序列,且是所有符合此條件序列中最長的,則S 稱為已知序列的最長公共子序列。

    舉個例子,如:有兩條隨機序列,如 1 3 4 5 5 and 2 4 5 6 5 7 6,則它們的最長公共子序列便是:4 5 5

LCS問題的解決思路


動態規劃演算法

    事實上,最長公共子序列問題也有最優子結構性質。

:

Xi=x1xi﹥即X序列的前i個字元 (1≤i≤m)(字首)

Yj=y1yj﹥即Y序列的前j個字元 (1≤j≤n)(字首)

假定Z=z1zk∈LCS(X , Y)

· 

xm=yn(最後一個字元相同),則不難用反證法證明:該字元必是XY的任一最長公共子序列Z(設長度為k)的最後一個字元,即有zk = xm = yn 且顯然有Zk-1∈LCS(Xm-1 , Yn-1)Z的字首Zk-1Xm-1Yn-1的最長公共子序列。此時,問題化歸成求Xm-1Yn-1LCSLCS(X , Y)的長度等於LCS(Xm-1 , Yn-1)的長度加1)。

· 

· 

xm≠yn,則亦不難用反證法證明:要麼Z∈LCS(Xm-1, Y),要麼Z∈LCS(X , Yn-1)。由於zk≠xmzk≠yn其中至少有一個必成立,若zk≠xm則有Z∈LCS(Xm-1 , Y),類似的,若zk≠yn 則有Z∈LCS(X , Yn-1)。此時,問題化歸成求Xm-1YLCSXYn-1LCSLCS(X , Y)的長度為:max{LCS(Xm-1 , Y)的長度, LCS(X , Yn-1)的長度}

· 

    由於上述當xm≠yn的情況中,求LCS(Xm-1 , Y)的長度與LCS(X , Yn-1)的長度,這兩個問題不是相互獨立的:兩者都需要求LCS(Xm-1Yn-1)的長度。另外兩個序列的LCS中包含了兩個序列的字首的LCS,故問題具有最優子結構性質考慮用動態規劃法。

    也就是說,解決這個LCS問題,你要求三個方面的東西:1、LCSXm-1Yn-1+12、LCSXm-1Y),LCSXYn-1);3、max{LCSXm-1Y),LCSXYn-1}

    行文至此,其實對這個LCS的動態規劃解法已敘述殆盡,不過,為了成書的某種必要性,下面,我試著再多加詳細闡述這個問題。

第三節、動態規劃演算法解LCS問題

3.1、最長公共子序列的結構

    最長公共子序列的結構有如下表示:

    設序列X=<x1, x2, …, xm>Y=<y1, y2, …, yn>的一個最長公共子序列Z=<z1, z2, …, zk>,則:

1. xm=yn,則zk=xm=ynZk-1Xm-1Yn-1的最長公共子序列;

2. xm≠ynzk≠xZXm-1Y的最長公共子序列;

3. xm≠ynzk≠yn ,則ZXYn-1的最長公共子序列。

    其中Xm-1=<x1, x2, …, xm-1>Yn-1=<y1, y2, …, yn-1>Zk-1=<z1, z2, …, zk-1>

32.子問題的遞迴結構

    由最長公共子序列問題的最優子結構性質可知,要找出X=<x1, x2, …, xm>Y=<y1, y2, …, yn>的最長公共子序列,可按以下方式遞迴地進行:當xm=yn時,找出Xm-1和Yn-1的最長公共子序列,然後在其尾部加上xm(=yn)即可得XY的一個最長公共子序列。當xm≠yn時,必須解兩個子問題,即找出Xm-1和Y的一個最長公共子序列及XYn-1的一個最長公共子序列。這兩個公共子序列中較長者即為XY的一個最長公共子序列。

    由此遞迴結構容易看到最長公共子序列問題具有子問題重疊性質。例如,在計算XY的最長公共子序列時,可能要計算出XYn-1及Xm-1和Y的最長公共子序列。而這兩個子問題都包含一個公共子問題,即計算Xm-1和Yn-1的最長公共子序列。

    與矩陣連乘積最優計算次序問題類似,我們來建立子問題的最優值的遞迴關係。用c[i,j]記錄序列Xi和Yj的最長公共子序列的長度。其中Xi=<x1, x2, …, xi>Yj=<y1, y2, …, yj>。當i=0j=0時,空序列是Xi和Yj的最長公共子序列,故c[i,j]=0。其他情況下,由定理可建立遞迴關係如下:

程式碼如下:

  1. 1. #include <cstdio>    
  2. 2. #include <iostream>    
  3. 3. #include <algorithm>    
  4. 4. #include <cstring>    
  5. 5. usingnamespace std;    
  6. 6.      
  7. 7. int main()    
  8. 8. {    
  9. 9.     string str1,str2;    
  10. 10.     int dp[200][200];    
  11. 11.     while(cin>>str1>>str2)    
  12. 12.     {    
  13. 13.         memset(dp,0,sizeof(dp));    
  14. 14.      
  15. 15.         int la = str1.length();    //字串的長度
  16. 16.         int lb = str2.length();    
  17. 17.      
  18. 18.         for(int i = 1; i <= la; i++)    
  19. 19.             for(int j = 1; j <= lb; j++)    
  20. 相關推薦

    演算法 --DP動態規劃LISLCS

    1.http://blog.csdn.net/u013445530/article/details/45645307 DP問題是ACM裡面最難的,因為太考思維能力了,只有將狀態轉移方程推出來才能解決問題,DP問題也是面試的時候最容易考到的,希望大家好好學DP,至少在面試的時

    演算法動態規劃動態規劃DP詳解

    一、基本概念 動態規劃(dynamic programming)是運籌學的一個分支,是求解決策過程(decision process)最優化的數學方法。20世紀50年代初美國數學家R.E.Bellman等人在研究多階段決策過程(multistep d

    dp動態規劃分類詳解

    fun balance card 給定 def bits eve 回文串 好的 dp動態規劃分類詳解 轉自:http://blog.csdn.NET/cc_again/article/details/25866971 動態規劃一直是ACM競賽中的重點,同時又是難點,因為

    九章演算法高階班筆記6.動態規劃

    區間類DP Stone Game Burst Ballons Scramble String 匹配類動規 Longest Common Subsequence Edit Distance K Edit Distance Distinct Subqu

    九章演算法高階班筆記5.動態規劃

    大綱: cs3k.com 滾動陣列 House Robber I/II Maximal Square 記憶化搜尋 Longest Increasing Subsequence Coin in a line I/II/III   什麼是動態

    單調佇列優化DP動態規劃1

    一.前言 學好了單調佇列不僅可以單獨使用,他還可以有更多的廣泛用途。這裡我主要講其對動態規劃的優化,較簡單。 二.例題1:單調佇列本身的靈活應用——測量溫度 在講用單調佇列優化DP前要先講一講單調佇列本身的靈活應用,所以引入一道題目——測量溫度。 1.題目 題目描述

    演算法課堂實驗報告——python動態規劃最長公共子序列LCS問題

    python實現動態規劃 一、開發環境 開發工具:jupyter notebook 並使用vscode,cmd命令列工具協助程式設計測試演算法,並使用codeblocks輔助編寫C++程式 程式語言:python3.6 二、實驗內容 1.最長公共子序列問題。分別求x=

    caioj1063·動態規劃入門維一邊推1:美元馬克

    1063:動態規劃入門(一維一邊推1:美元和馬克) 時間限制: 1 Sec 記憶體限制: 128 MB 題目描述 【問題描述】 今天6:00起床,我轉身發現枕頭邊有100美元。 出門的時候發現門口有家冰淇淋店,拉了很長的橫幅:“今天100美元和4

    Just Too Lucky個數能被其各位的整除

    題目連結https://vjudge.net/problem/Gym-100623J 題目大意:求1-n中,有多少個數,可以被其各位的和整除 n<=1e12   設x為數位和 x的範圍為[1,108] 也就是說要讓一個數n mod x = 0 &nbs

    文弄懂動態規劃DP Dynamic Programming下樓梯,國王金礦,揹包問題,Dijkstra演算法

    動態規劃 參考連結 漫畫演算法,什麼是動態規劃? DP 動態規劃是一種分階段求解決策問題的數學思想 題目一 問:下樓梯問題,有一座高度是10級臺階的樓梯,從下往上走,每跨一步只能向上1級或者2級臺階,請問有多少中走法。 思路 剛才這個題目,你每走一步就有兩

    演算法設計與分析——動態規劃矩陣連乘

    動態規劃——Dynamic programming,可以說是本人一直沒有啃下的骨頭,這次我就得好好來學學Dynamic programming. OK,出發! 動態規劃通常是分治演算法的一種特殊情況,它一般用於最優化問題,如果這些問題能夠: 1.能夠分解為規模更小的子問題 2.遞迴的

    動態規劃DP演算法

        動態規劃相信大家都知道,動態規劃演算法也是新手在剛接觸演算法設計時很苦惱的問題,有時候覺得難以理解,但是真正理解之後,就會覺得動態規劃其實並沒有想象中那麼難。網上也有很多關於講解動態規劃的文章,大多都是敘述概念,講解原理,讓人覺得晦澀難懂,即使一時

    由Leetcode詳解演算法動態規劃DP

    因為最近一段時間接觸了一些Leetcode上的題目,發現許多題目的解題思路相似,從中其實可以瞭解某類演算法的一些應用場景。 這個隨筆系列就是我嘗試的分析總結,希望也能給大家一些啟發。 動態規劃的基本概念 一言以蔽之,動態規劃就是將大問題分成小問題,以迭代的方式求解。 可以使用動態規劃求解的問題

    演算法總結之動態規劃DP

    適用動態規劃的特點 所解決的問題是最優化問題。 所解決的問題具有“最優子結構”。可以建立一個遞推關係,使得n階段的問題,可以通過幾個k<n階段的低階子問題的最優解來求解。 具有“重疊子結構”的特點。即,求解低階子問題時存在重複計算。 詞典法 大家都知道,遞迴演算法一般都存在大量的重複計算,這會造成不

    Dijkstra演算法,求最短路dp 動態規劃

    •迪傑斯特拉(Dijkstra)演算法思想 按路徑長度遞增次序產生最短路徑演算法: 把V分成兩組: (1)S:已求出最短路徑的頂點的集合 (2)V-S=T:尚未確定最短路徑的頂點集合 將T中頂點按最短路徑遞增的次序加入到S中, 保證:(1)從源點V0到S中各

    演算法筆記:動態規劃DP初步

    專題:動態規劃(DP)初步 內容來源:《挑戰程式設計競賽》(第2版)+《演算法競賽入門經典》(第2版)+網上資料整理彙總 一、引入         動態規劃程式設計是對解最優化問題的一種途徑、一種方法,而不是一種特殊演算法。不像前面所述的那些搜尋或數值計算那樣,具有

    演算法動態規劃DP

    前言 前言_ 我們遇到的問題中,有很大一部分可以用動態規劃(簡稱DP)來解。 解決這類問題可以很大地提升你的能力與技巧,我會試著幫助你理解如何使用DP來解題。 這篇文章是基於例項展開來講的,因為乾巴巴的理論實在不好理解。 注意:如果你對於其中某一節已經瞭解並且不

    五個常用演算法動態規劃

    1.從01揹包問題說起 有一堆寶石一共n個,現在你身上能裝寶石的就只有一個揹包,揹包的容量為C。把n個寶石排成一排並編上號: 0,1,2,…,n-1。第i個寶石對應的體積和價值分別為V[i]和W[i] 。揹包總共也就只能裝下體積為C的東西,那你要裝下哪些寶石才能獲得最大的

    動態規劃DP

    first 個數 目的 進入 right 返回值 ase lines cal 在學習動態規劃前 , 先補充些有關遞歸的知識 。      所謂的遞歸函數 就是調用自身函數的過程 ,因此是用棧來存儲的 。   遞歸函數的最終返回值 就是第一次調用函數的返回值 。   在寫

    初探動態規劃DP

    isp exit min 管理 應該 一行 註意 習慣 試圖 學習qzz的命名,來寫一篇關於動態規劃(dp)的入門博客。 動態規劃應該算是一個入門oier的坑,動態規劃的抽象即神奇之處,讓很多萌新 萌比。 寫這篇博客的目標,就是想要用一些容易理解的方式,講解入門動態規劃的真