學習筆記之動態規劃(一)最長上升子序列及最長公共子序列

例題:最長上升子序列(百練2757)
問題描述
一個數的序列ai,當a 1 < a 2 < … < a S 的時候,我們稱這個序列是上升的。對於給定的一個序列(a 1 , a 2 , …, a N ),我們可以得到一些上升的子序列(a i1 , a i2 , …, a iK ),這裏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。
輸出要求
最長上升子序列的長度。
輸入樣例
7
1 7 3 5 9 4 8
輸出樣例
4

  1. 找子問題
    “求序列的前n個元素的最長上升子序列的長度”是個子問題,但這樣分解子問題,不具有“無後效性”假設F(n) = x,但可能有多個序列滿足F(n) = x。有的序列的最後一個元素比 a n+1 小,則加上a n+1 就能形成更長上升子序列;有的序列最後一個元素不比a n+1 小……以後的事情受如何達到狀態n的影響,不符合“無後效性”
  2. 找子問題
    “求以a k (k=1, 2, 3…N)爲終點的最長上升子序列的長度”一個上升子序列中最右邊的那個數,稱爲該子序列的“終點”。
    雖然這個子問題和原問題形式上並不完全一樣,但是隻要這N個子問題都解決了,那麼這N個子問題的解中,最大的那個就是整個問題的解。
  3. 確定狀態:
    子問題只和一個變量-- 數字的位置相關。因此序列中數的位置k 就是“狀態”,而狀態 k 對應的“值”,就是以a k做爲“終點”的最長上升子序列的長度。狀態一共有N個。
  4. 找出狀態轉移方程:
    maxLen (k)表示以a k 做爲“終點”的最長上升子序列的長度那麼:
    初始狀態:maxLen (1) = 1
    maxLen (k) = max { maxLen (i):1<=i < k 且 a i < a k 且 k≠1 } + 1若找不到這樣的i,則maxLen(k) = 1maxLen(k)的值,就是在a k 左邊,“終點”數值小於a k ,且長度最大的那個上升子序列的長度再加1。因爲a k 左邊任何“終點”小於a k 的子序列,加上a k 後就能形成一個更長的上升子序列。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN =1010;
int a[MAXN]; int maxLen[MAXN];
int main() {
	int N; cin >> N;
		for( int i = 1;i <= N;++i) {
			cin >> a[i]; maxLen[i] = 1;
}
	for( int i = 2; i <= N; ++i) {// 每次求以第i 個數爲終點的最長上升子序列的長度
		for( int j = 1; j < i; ++j)// 察看以第j 個數爲終點的最長上升子序列
		if( a[i] > a[j] )
			maxLen[i] = max(maxLen[i],maxLen[j]+1);
	}
		cout << * max_element(maxLen+1,maxLen + N + 1 );
return 0;
} 

動歸的常用兩種形式
1)遞歸型
優點:直觀,容易編寫
缺點:可能會因遞歸層數太深導致爆棧,函數調用帶來額外時間開銷。無法使用滾動數組節省空間。總體來說,比遞推型慢。
1)遞推型
效率高,有可能使用滾動數組節省空間

例、最長公共子序列(POJ1458)
給出兩個字符串,求出這樣的一個最長的公共子序列的長度:子序列中的每個字符都能在兩個原串中找到,而且每個字符的先後順序和原串中的先後順序一致。

Sample Input
abcfbc abfcab
programming contest
abcd mnp
Sample Output
4
2
0
輸入兩個串s1,s2,
設MaxLen(i,j)表示:
s1的左邊i個字符形成的子串,與s2左邊的j個字符形成的子串的最長公共子序列的長度(i,j從0開始算)
MaxLen(i,j) 就是本題的“狀態”假定 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;
else
MaxLen(i,j) = Max(MaxLen(i,j-1),MaxLen(i-1,j) );
時間複雜度O(mn) m,n是兩個字串長度,S1長度爲 i,S2長度爲 j,S1[i-1]!= s2[j-1]時,MaxLen(S1,S2)不會比MaxLen(S1,S2 j-1 )和MaxLen(S1 i-1 ,S2)兩者之中任何一個小,也不會比兩者都大。

#include <iostream>
#include<cstring>
using namespace std;

char s1[1000],s2[1000];
int maxlen[1000][1000];
int main(){
cin>>s1>>s2;
int s1n=strlen(s1),s2n=strlen(s2);
for(int i=0;i<=s1n;++i)
	maxlen[i][0]=0;
for(int i=0;i<=s2n;++i)
	maxlen[0][i]=0;
for(int i=1;i<=s1n;++i)
	for(int j=1;j<=s2n;++j){
		if(s1[i-1]==s2[j-1])maxlen[i][j]=maxlen[i-1][j-1]+1;
		else maxlen[i][j]=max(maxlen[i-1][j],maxlen[i][j-1]);
	}
	cout<<maxlen[s1n][s2n]<<endl;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章