教你徹底學會動態規劃 高階篇

2021-10-10 01:12:31 字數 4089 閱讀 8834

話不多說,來看如下例題,也是在動態規劃裡面遇到過的最頻繁的乙個題,本題依然來自於北大poj:

最長公共子串行(poj1458)

給出兩個字串,求出這樣的乙個最長的公共子串行的長度:子串行中的每個字元都能在兩個原串中找到, 而且每個字元的先後順序和原串中的先後順序一致。

sample input

abcfbc abfcab

programming    contest

abcd    mnp

sample output

假定len1 = strlen(s1),len2 = strlen(s2),那麼題目就是要求maxlen(len1,len2)

顯然:maxlen(n,0)  = 0  ( n= 0…len1)

maxlen(0,n)  = 0  ( n=0…len2)

於是,我們可以得到如下的遞推公式:  

if ( s1[i-1] == s2[j-1] ) //s1的最左邊字元是s1[0]maxlen(i,j) = maxlen(i-1,j-1) + 1;elsemaxlen(i,j) = max(maxlen(i,j-1),maxlen(i-1,j) );

時間複雜度o(mn) m,n是兩個字串長度

s1[i-1]!= s2[j-1]時,maxlen(s1,s2)不會比maxlen(s1,s2j-1) 和maxlen(s1i-1,s2)兩者之中任何乙個小,也不會比兩者都大。

通過上面的分析,我們很簡單就可以寫出如下的**:  

#include #include using namespace std; 

char sz1[1000];

char sz2[1000];

int maxlen[1000][1000];

int main()

}

cout << maxlen[length1][length2] << endl;

} return 0;

}

然後提交我們的**,一次ac。

接下來我們再來看一道典型的例題:

最長上公升子串行(百練2757)

乙個數的序列ai,當a1 < a2 < ... < as的時候,我們稱這個序列是上公升的。對於給定的乙個序列(a1, a2, ..., an),我們可以得到一些上公升的子串行(ai1, ai2, ..., aik),這裡1 <= i1 < i2 < ... < ik <= n。比如,對於序列(1, 7, 3, 5, 9, 4, 8), 有它的一些上公升子串行,如(1, 7), (3, 4, 8)等等。這些子串行中最長的長度是4,比如子串行(1, 3, 5, 8).。

你的任務,就是對於給定的序列,求出最長上公升子串行的長度。

輸入資料

輸入的第一行是序列的長度n (1 <= n <= 1000)。第二行給出序列中的n個整數,這些整數的取值範圍都在0到10000。

輸出要求

最長上公升子串行的長度。

輸入樣例

1 7 3 5 9 4 8

輸出樣例

解題思路

1.找子問題

「求序列的前n個元素的最長上公升子串行的長度」是個子問題,但這樣分解子問題,不具有「無後效性」,因為假設f(n) = x,但可能有多個序列滿足f(n) = x。有的序列的最後乙個元素比 an+1小,則加上an+1就能形成更長上 公升子串行;有的序列最後乙個元素不比an+1小……以後的事情受如何達到狀態n的影響,不符合「無後效性」 ,因此我們必須換一種思路來解決此問題。

「求以ak(k=1, 2, 3…n)為終點的最長上公升子串行的長度」,乙個上公升子串行中最右邊的那個數,稱為該子串行的 「終點」。雖然這個子問題和原問題形式上並不完全一樣,但是只要這n個子問題都解決了,那麼這n個子問題的解中, 最大的那個就是整個問題的解。

2.確定狀態

子問題只和乙個變數—— 數字的位置相關。因此序列中數的位置k就是「狀態」,而狀態 k 對應的「值」,就是以ak做為「終點」的最長上公升子串行的長度。 狀態一共有n個。

3.找出狀態轉移方程

maxlen (k)表示以ak做為「終點」的

最長上公升子串行的長度那麼:

初始狀態:maxlen (1) = 1

maxlen (k) = max + 1       若找不到這樣的i,則maxlen(k) = 1

maxlen(k)的值,就是在ak左邊,「終點」數值小於ak ,且長度最大的那個上公升子串行的長度再加1。因為ak左邊任何「終點」小於ak的子串行,加上ak後就能形成乙個更長的上公升子串行。

有了這個思路,我們就可以很輕鬆地寫出**了。然而,即使到了這裡,我們依然還能從兩個方向解決這道題,我們可以將它們分別稱為「人人為我」遞推型動歸和「我為人人」遞推型動歸 。請看下面的講解:

「人人為我」遞推型動歸

狀態i的值fi由若干個值 已知的狀態值fk,fm,..fy 推出,如求和,取最大值

根據這個方向,我們不難寫出如下**:  

#include #include #include using namespace std; 

const int maxn =1010;

int a[maxn]; int maxlen[maxn];

int main()

for( int i = 2; i <= n; ++i)

cout << * max_element(maxlen+1,maxlen + n + 1 );

return 0;

} //時間複雜度o(n2)

「我為人人」遞推型動歸狀態i的值fi在被更新(不一定是 最終求出)的時候,依據fi去更 新(不一定是最終求出)和狀態i 相關的其他一些狀態的值 fk,fm,..fy

根據這個方向,我們又可以寫出如下**:  

#include #include #include using namespace std; 

const int maxn =1010;

int a[maxn];

int maxlen[maxn];

int main()

for( int i = 1; i <= n; ++i)

for( int j = i + 1; j <= n; ++j ) //看看能更新哪些狀態的值

if( a[j] > a[i] )

maxlen[j] = max(maxlen[j],maxlen[i]+1);

cout << * max_element(maxlen+1,maxlen + n + 1 );

return 0;

} //時間複雜度o(n2)

接下來,就要進行乙個總結了:

動規的三種形式

1)記憶遞迴型

優點:只經過有用的狀態,沒有浪費。遞推型會檢視一些 沒用的狀態,有浪費。

缺點:可能會因遞迴層數太深導致棧溢位,函式呼叫帶來額外時間開銷。總體來說,比遞推型慢。

2) 「我為人人」遞推型

沒有什麼明顯的優勢,有時比較符合思考的習慣。個別特殊題目中會比「人人為我」型節省空間。

3)「人人為我」遞推型

在選取最優備選狀態的值fm,fn,…fy時, 有可能有好的演算法或資料結構可以用來顯 著降低時間複雜度。

教你徹底學會動態規劃 入門篇

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

為什麼你不會動態規劃?

動態規劃 dynamic programming 是刷題中最常見也最重要的乙個類別,在研發崗位面試中往往佔據著重要地位。對於刷題,要寫在前面的是肯定要通過大量的練習和自身體會去把握每種題型的思路,這是大前提。不過動態規劃 以下用dp代替 屬於沒有公式的題型,這種題的特點是可能你花了很長時間去練習,卻...

高階動態規劃

高階dp為什麼複雜,第一 狀態定義 第二 狀態轉移方程 它的複雜度 於什麼地方 1 狀態擁有更多維度 二維,三維,或者更多,甚至需要壓縮 比如斐波那契只需要兩個變數 每個維度是什麼,邏輯清晰 2 狀態轉移方程 編輯距離 給你兩個單詞 word1 和 word2,請你計算出將 word1 轉換成 wo...