Common Subsequence
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 17390 Accepted Submission(s): 7290
Problem DescriptionA subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = <x1, x2, ..., xm> another sequence Z = <z1, z2, ..., zk> is a subsequence of X if there exists a strictly increasing
sequence <i1, i2, ..., ik> of indices of X such that for all j = 1,2,...,k, xij = zj. For example, Z = <a, b, f, c> is a subsequence of X = <a, b, c, f, b, c> with index sequence <1, 2, 4, 6>. Given two sequences X and Y the problem is to find the length of
the maximum-length common subsequence of X and Y.
The program input is from a text file. Each data set in the file contains two strings representing the given sequences. The sequences are separated by any number of white spaces. The input data are correct. For each set of data the program prints on the standard
output the length of the maximum-length common subsequence from the beginning of a separate line.
Sample Input
abcfbc abfcabprogramming contest abcd mnp
Sample Output
420
SourceSoutheastern Europe 2003
RecommendIgnatius
解題思路:本題是求兩個字串的最長公用子序列。
先用公用子串引入處理方法
最長公用子串(LCS)
找 兩個字串的最長公用子串,這個子串要求在原字串中是連續的。其實這又是一個序貫決策問題,可以用動態規劃來求解。我們採用一個二維矩陣來記錄中間的結 果。這個二維矩陣怎麼構造呢?直接舉個例子吧:"bab"和"caba"(當然我們現在一眼就可以看出來最長公用子串是"ba"或"ab")
b a b
c 0 0 0
a 0
1 0
b 1
0 1
a 0 1 0
我們看矩陣的斜對角線最長的那個就能找出最長公用子串。
不過在二維矩陣上找最長的由1組成的斜對角線也是件麻煩費時的事,下面改進:當要在矩陣是填1時讓它等於其左上方元素加1。
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
這樣矩陣中的最大元素就是 最長公用子串的長度。
在構造這個二維矩陣的過程中由於得出矩陣的某一行後其上一行就沒用了,所以實際上在程式中可以用一維數組來代替這個矩陣。
同理處理最長公用子序列
最長公用子序列
最長公用子序列與最長公用子串的區別在於最長公用子序列不要求在原字串中是連續的,比如ADE和ABCDE的最長公用子序列是ADE。
我們用動態規劃的方法來思考這個問題如是求解。首先要找到狀態轉移方程:
等號約定,C1是S1的最右側字元,C2是S2的最右側字元,S1‘是從S1中去除C1的部分,S2'是從S2中去除C2的部分。
LCS(S1,S2)等於下列3項的最大者:
(1)LCS(S1,S2’)
(2)LCS(S1’,S2)
(3)LCS(S1’,S2’)--如果C1不等於C2; LCS(S1',S2')+C1--如果C1等於C2;
邊界終止條件:如果S1和S2都是空串,則結果也是空串。
下面我們同樣要構建一個矩陣來儲存動態規划過程中子問題的解。這個矩陣中的每個數字代表了該行和該列之前的LCS的長度。與上面剛剛分析出的狀態轉移議程相對應,矩陣中每個格子裡的數字應該這麼填,它等於以下3項的最大值:
(1)上面一個格子裡的數字
(2)左邊一個格子裡的數字
(3)左上方那個格子裡的數字(如果 C1不等於C2); 左上方那個格子裡的數字+1( 如果C1等於C2)
舉個例子:
G C T A
0 0 0 0 0
G 0 1 1 1 1
B 0 11
1 1
T 0 1 1 2 2
A 0 1 1 2 3
填寫最後一個數字時,它應該是下面三個的最大者:
(1)上邊的數字2
(2)左邊的數字2
(3)左上方的數字2+1=3,因為此時C1==C2
所以最終結果是3。
在填寫過程中我們還是記錄下目前的儲存格的數字來自於哪個儲存格,以方便最後我們回溯找出最長公用子串。有時候左上、左、上三者中有多個同時達到最大,那麼任取其中之一,但是在整個過程中你必須遵循固定的優先標準。在My Code中優先順序別是左上>左>上。
給出了回溯法找出LCS的過程:
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int num[1002][1002];int main(){ int i,j,k; char a[1002],b[1002]; while(scanf("%s",a)!=EOF) { scanf("%s",b); int stra=strlen(a); int strb=strlen(b); memset(num,0,sizeof(num)); for(i=1;i<=stra;i++) { for(j=1;j<=strb;j++) { k=num[i-1][j-1]; if(a[i-1]==b[j-1]) k++; num[i][j]=max(max(num[i][j-1],num[i-1][j]),k); //狀態方程 } } printf("%d\n",num[stra][strb]); } return 0;}