現在如果將問題中的字串改成頭尾相接環狀的字串,我們稱其最長共同子序列為CLCS,例如:
設A,B為頭尾相接環狀字串
A = "aabaa" , B = "aaabb"
CLCS(A,B) = "aaab"
像這樣的問題要怎麼解呢?剛好codeforces上面就有一題:http://codeforces.com/gym/100203/problem/B
為了解決這一題,查閱了不少奇怪的文章,最後給我找到了這篇論文:Solving Cyclic Longest Common Subsequence in Quadratic Time
對於CLCS提出了\ord{nm}的演算法,在這裡附上該演算法的實作:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<cstring> | |
#include<algorithm> | |
using std::max; | |
const int MAXN = 1505; | |
enum traceType{LEFT,DIAG,UP}; | |
int dp[MAXN*2][MAXN], pa[MAXN*2][MAXN]; | |
char AA[MAXN*2]; | |
void LCS(const char *a, const char *b, int m, int n){ | |
for(int i=1; i<=m; ++i) | |
for(int j=1; j<=n; ++j){ | |
if(a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1]+1; | |
else dp[i][j]=max(dp[i][j-1], dp[i-1][j]); | |
if(dp[i][j]==dp[i][j-1]) pa[i][j]=LEFT; | |
else if(a[i-1]==b[j-1]) pa[i][j]=DIAG; | |
else pa[i][j]=UP; | |
} | |
} | |
int trace(int m, int n){ | |
int res = 0; | |
while(m&&n){ | |
if(pa[m][n]==LEFT) --n; | |
else if(pa[m][n]==UP) --m; | |
else --m, --n, ++res; | |
} | |
return res; | |
} | |
void reRoot(int root,int m, int n){ | |
int i=root, j=1; | |
while(j<=n&&pa[i][j]!=DIAG) ++j; | |
if(j>n) return; | |
pa[i][j] = LEFT; | |
while(i<m&&j<n){ | |
if(pa[i+1][j]==UP) pa[++i][j]=LEFT; | |
else if(pa[i+1][j+1]==DIAG) | |
pa[++i][++j]=LEFT; | |
else ++j; | |
} | |
while(i<m&&pa[++i][j]==UP) pa[i][j]=LEFT; | |
} | |
int CLCS(const char *a, const char *b){ | |
int m=strlen(a), n=strlen(b); | |
strcpy(AA,a); strcpy(AA+m,a); | |
LCS(AA,b,m*2,n); | |
int ans = dp[m][n]; | |
for(int i=1; i<m; ++i){ | |
reRoot(i,m*2,n); | |
ans=max(ans,trace(m+i,n)); | |
} | |
return ans; | |
} |
作法大致上是將b字串變成原來的兩倍,然後做一般的LCS,但在過程中要注意更新答案的順序。接著修改DP來源表格,每次將來源表格的第一個row刪掉(reRoot函數),然後計算當前的答案。
當我AC這一題後,我看到了一個非常驚人的作法,也是用DP複雜度是\ord{nm},但是我目前還無法完全理解為何這樣是可行的,而且這樣的做法我也不知道如何回溯找出一組解,只能知道CLCS的長度,希望路過的大大們能提供想法:
當我AC這一題後,我看到了一個非常驚人的作法,也是用DP複雜度是\ord{nm},但是我目前還無法完全理解為何這樣是可行的,而且這樣的做法我也不知道如何回溯找出一組解,只能知道CLCS的長度,希望路過的大大們能提供想法:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<cstring> | |
#include<algorithm> | |
const int MAXN=1505; | |
int h[MAXN][MAXN*2], v[MAXN][MAXN*2]; | |
char B[MAXN*2]; | |
int CLCS(const char *a, const char *b){ | |
int m = strlen(a), n = strlen(b); | |
strcpy(B,b); strcpy(B+n,b); | |
for(int j=0; j<n*2; ++j) h[0][j] = j+1; | |
for(int i=0; i<m; ++i){ | |
for(int j=0; j<n*2; ++j){ | |
if(a[i]==B[j]){ | |
h[i+1][j] = v[i][j]; | |
v[i][j+1] = h[i][j]; | |
}else{ | |
h[i+1][j] = std::max(h[i][j], v[i][j]); | |
v[i][j+1] = std::min(h[i][j], v[i][j]); | |
} | |
} | |
} | |
int ans=0; | |
for(int i=0; i<n; ++i){ | |
int sum = 0; | |
for(int j=0; j<n; ++j) | |
if (h[m][j+i] <= i) ++sum; | |
ans = std::max(ans,sum); | |
} | |
return ans; | |
} |