日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

DP(动态规划)

發布時間:2023/11/27 生活经验 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 DP(动态规划) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

http://www.hawstein.com/posts/dp-novice-to-advanced.html

https://www.topcoder.com/community/data-science/data-science-tutorials/dynamic-programming-from-novice-to-advanced/

感謝作者!

狀態和狀態轉移方程

Longest Increasing Subsequence

初級

上面討論了一個非常簡單的例子。現在讓我們來看看對于更復雜的問題, 如何找到狀態之間的轉移方式(即找到狀態轉移方程)。 為此我們要引入一個新詞叫遞推關系來將狀態聯系起來(說的還是狀態轉移方程)

OK,上例子,看看它是如何工作的。

一個序列有N個數:A[1],A[2],…,A[N],求出最長非降子序列的長度。 (講DP基本都會講到的一個問題LIS:longest 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個數的序列是:

5,3,4,8,6,7

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

  • 前1個數的LIS長度d(1)=1(序列:5)
  • 前2個數的LIS長度d(2)=1(序列:3;3前面沒有比3小的)
  • 前3個數的LIS長度d(3)=2(序列:3,4;4前面有個比它小的3,所以d(3)=d(2)+1)
  • 前4個數的LIS長度d(4)=3(序列:3,4,8;8前面比它小的有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序列)

Talk is cheap, show me the code:

#include <iostream>
using namespace std; int lis(int A[], int n){ int *d = new int[n]; int len = 1; for(int i=0; i<n; ++i){ d[i] = 1; for(int j=0; j<i; ++j) if(A[j]<=A[i] && d[j]+1>d[i]) d[i] = d[j] + 1; if(d[i]>len) len = d[i]; } delete[] d; return len; } int main(){ int A[] = { 5, 3, 4, 8, 6, 7 }; cout<<lis(A, 6)<<endl; return 0; } 

該算法的時間復雜度是O(n^2 ),并不是最優的解法。 還有一種很巧妙的算法可以將時間復雜度降到O(nlogn),網上已經有各種文章介紹它, 這里就不再贅述。傳送門:LIS(O(nlogn)的解法。 此題還可以用“排序+LCS”來解,感興趣的話可自行Google。

練習題

無向圖G有N個結點(1<N<=1000)及一些邊,每一條邊上帶有正的權重值。 找到結點1到結點N的最短路徑,或者輸出不存在這樣的路徑。

提示:在每一步中,對于那些沒有計算過的結點, 及那些已經計算出從結點1到它的最短路徑的結點,如果它們間有邊, 則計算從結點1到未計算結點的最短路徑。

嘗試解決以下來自topcoder競賽的問題:

  • ZigZag?- 2003 TCCC Semifinals 3
  • BadNeighbors?- 2004 TCCC Round 4
  • FlowerGarden?- 2004 TCCC Round 1

中級

接下來,讓我們來看看如何解決二維的DP問題。

平面上有N*M個格子,每個格子中放著一定數量的蘋果。你從左上角的格子開始, 每一步只能向下走或是向右走,每次走到一個格子上就把格子里的蘋果收集起來, 這樣下去,你最多能收集到多少個蘋果。

解這個問題與解其它的DP問題幾乎沒有什么兩樣。第一步找到問題的“狀態”, 第二步找到“狀態轉移方程”,然后基本上問題就解決了。

首先,我們要找到這個問題中的“狀態”是什么?我們必須注意到的一點是, 到達一個格子的方式最多只有兩種:從左邊來的(除了第一列)和從上邊來的(除了第一行)。 因此為了求出到達當前格子后最多能收集到多少個蘋果, 我們就要先去考察那些能到達當前這個格子的格子,到達它們最多能收集到多少個蘋果。 (是不是有點繞,但這句話的本質其實是DP的關鍵:欲求問題的解,先要去求子問題的解)

經過上面的分析,很容易可以得出問題的狀態和狀態轉移方程。 狀態S[i][j]表示我們走到(i, j)這個格子時,最多能收集到多少個蘋果。那么, 狀態轉移方程如下:

S[i][j]=A[i][j] + max(S[i-1][j], if i>0 ; S[i][j-1], if j>0)

其中i代表行,j代表列,下標均從0開始;A[i][j]代表格子(i, j)處的蘋果數量。

S[i][j]有兩種計算方式:1.對于每一行,從左向右計算,然后從上到下逐行處理;2. 對于每一列,從上到下計算,然后從左向右逐列處理。 這樣做的目的是為了在計算S[i][j]時,S[i-1][j]和S[i][j-1]都已經計算出來了。

偽代碼如下:

以下兩道題來自topcoder,練習用的。

  • AvoidRoads?- 2003 TCO Semifinals 4
  • ChessMetric?- 2003 TCCC Round 4

中高級

這一節要討論的是帶有額外條件的DP問題。

以下的這個問題是個很好的例子。

無向圖G有N個結點,它的邊上帶有正的權重值。

你從結點1開始走,并且一開始的時候你身上帶有M元錢。如果你經過結點i, 那么你就要花掉S[i]元(可以把這想象為收過路費)。如果你沒有足夠的錢, 就不能從那個結點經過。在這樣的限制條件下,找到從結點1到結點N的最短路徑。 或者輸出該路徑不存在。如果存在多條最短路徑,那么輸出花錢數量最少的那條。 限制:1<N<=100 ; 0<=M<=100 ; 對于每個i,0<=S[i]<=100;正如我們所看到的, 如果沒有額外的限制條件(在結點處要收費,費用不足還不給過),那么, 這個問題就和經典的迪杰斯特拉問題一樣了(找到兩結點間的最短路徑)。 在經典的迪杰斯特拉問題中, 我們使用一個一維數組來保存從開始結點到每個結點的最短路徑的長度, 即M[i]表示從開始結點到結點i的最短路徑的長度。然而在這個問題中, 我們還要保存我們身上剩余多少錢這個信息。因此,很自然的, 我們將一維數組擴展為二維數組。M[i][j]表示從開始結點到結點i的最短路徑長度, 且剩余j元。通過這種方式,我們將這個問題規約到原始的路徑尋找問題。 在每一步中,對于已經找到的最短路徑,我們找到它所能到達的下一個未標記狀態(i,j), 將它標記為已訪問(之后不再訪問這個結點),并且在能到達這個結點的各個最短路徑中, 找到加上當前邊權重值后最小值對應的路徑,即為該結點的最短路徑。 (寫起來真是繞,建議畫個圖就會明了很多)。不斷重復上面的步驟, 直到所有的結點都訪問到為止(這里的訪問并不是要求我們要經過它, 比如有個結點收費很高,你沒有足夠的錢去經過它,但你已經訪問過它) 最后Min[N-1][j]中的最小值即是問題的答案(如果有多個最小值, 即有多條最短路徑,那么選擇j最大的那條路徑,即,使你剩余錢數最多的最短路徑)。

偽代碼:

下面有幾道topcoder上的題以供練習:

  • Jewelry?- 2003 TCO Online Round 4
  • StripePainter?- SRM 150 Div 1
  • QuickSums?- SRM 197 Div 2
  • ShortPalindromes?- SRM 165 Div 2

高級

以下問題需要仔細的揣摩才能將其規約為可用DP解的問題。

問題:StarAdventure?- SRM 208 Div 1:

給定一個M行N列的矩陣(M*N個格子),每個格子中放著一定數量的蘋果。 你從左上角的格子開始,只能向下或向右走,目的地是右下角的格子。 你每走過一個格子,就把格子上的蘋果都收集起來。然后你從右下角走回左上角的格子, 每次只能向左或是向上走,同樣的,走過一個格子就把里面的蘋果都收集起來。 最后,你再一次從左上角走到右下角,每過一個格子同樣要收集起里面的蘋果 (如果格子里的蘋果數為0,就不用收集)。求你最多能收集到多少蘋果。

注意:當你經過一個格子時,你要一次性把格子里的蘋果都拿走。

限制條件:1 < N, M <= 50;每個格子里的蘋果數量是0到1000(包含0和1000)。

如果我們只需要從左上角的格子走到右下角的格子一次,并且收集最大數量的蘋果, 那么問題就退化為“中級”一節里的那個問題。將這里的問題規約為“中級”里的簡單題, 這樣一來會比較好解。讓我們來分析一下這個問題,要如何規約或是修改才能用上DP。 首先,對于第二次從右下角走到左上角得出的這條路徑, 我們可以將它視為從左上角走到右下角得出的路徑,沒有任何的差別。 (即從B走到A的最優路徑和從A走到B的最優路徑是一樣的)通過這種方式, 我們得到了三條從頂走到底的路徑。對于這一點的理解可以稍微減小問題的難度。 于是,我們可以將這3條路徑記為左,中,右路徑。對于兩條相交路徑(如下圖):

在不影響結果的情況下,我們可以將它們視為兩條不相交的路徑:

這樣一來,我們將得到左,中,右3條路徑。此外,如果我們要得到最優解, 路徑之間不能相交(除了左上角和右下角必然會相交的格子)。因此對于每一行y( 除了第一行和最后一行),三條路徑對應的x坐標要滿足:x1[y] < x2[y] < x3[y]。 經過這一步的分析,問題的DP解法就進一步地清晰了。讓我們考慮行y, 對于每一個x1[y-1],x2[y-1]和x3[y-1],我們已經找到了能收集到最多蘋果數量的路徑。 根據它們,我們能求出行y的最優解。現在我們要做的就是找到從一行移動到下一行的方式。 令Max[i][j][k]表示到第y-1行為止收集到蘋果的最大數量, 其中3條路徑分別止于第i,j,k列。對于下一行y,對每個Max[i][j][k] 都加上格子(y,i),(y,j)和(y,k)內的蘋果數量。因此,每一步我們都向下移動。 我們做了這一步移動之后,還要考慮到,一條路徑是有可能向右移動的。 (對于每一個格子,我們有可能是從它上面向下移動到它, 也可能是從它左邊向右移動到它)。為了保證3條路徑互不相交, 我們首先要考慮左邊的路徑向右移動的情況,然后是中間,最后是右邊的路徑。 為了更好的理解,讓我們來考慮左邊的路徑向右移動的情況,對于每一個可能的j,k對(j<k), 對每個i(i<j),考慮從位置(i-1,j,k)移動到位置(i,j,k)。處理完左邊的路徑, 再處理中間的路徑,最后處理右邊的路徑。方法都差不多。

用于練習的topcoder題目:

  • MiniPaint?- SRM 178 Div 1

其它

當閱讀一個題目并且開始嘗試解決它時,首先看一下它的限制。 如果要求在多項式時間內解決,那么該問題就很可能要用DP來解。遇到這種情況, 最重要的就是找到問題的“狀態”和“狀態轉移方程”。(狀態不是隨便定義的, 一般定義完狀態,你要找到當前狀態是如何從前面的狀態得到的, 即找到狀態轉移方程)如果看起來是個DP問題,但你卻無法定義出狀態, 那么試著將問題規約到一個已知的DP問題(正如“高級”一節中的例子一樣)。

轉載于:https://www.cnblogs.com/guxuanqing/p/5638947.html

總結

以上是生活随笔為你收集整理的DP(动态规划)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。