LightOJ1157 LCS Revisited(DP)
题目求两个字符串s1,s2不同的LCS个数。
经典的求LCS的DP是这样的:
- LCS[i][j]表示s1[0...i]和s2[0...j]的LCS
- LCS[i][j]从LCS[i-1][j-1]+1(s1[i]==s2[j])或max(LCS[i-1][j],LCS[i][j-1])(s1[i]!=s2[j])转移来
计数的话也跟着转移,用dp[i][j]计数。不过搞不出。。看了别人的解法才恍然大悟,要减去多算的部分,即s1[i]!=s2[j]且LCS[i-1][j]等于LCS[i][j-1]时这种情况的转移,如果只是dp[i][j]=dp[i-1][j]+dp[i][j-1]可能会有重复算的部分:
- LCS[i-1][j]等于LCS[i][j-1],且LCS[i-1][j-1]不与它们相等,那样s1[0...i-1]和s2[0...j]所有的LCS必定以s2[j]为结尾,s1[0...i]和s2[0...j-1]同理,所以s1[0...i-1]和s2[0...j]所有的LCS和s1[0...i]和s2[0...j-1]的所有LCS没有相交部分,dp[i][j]=dp[i-1][j]+dp[i][j-1];
- 而如果LCS[i-1][j-1]等于它们的情况,就说明存在不以s1[i]和s2[j]结尾的LCS,多算了一次s1[0...i-1]和s2[0...j-1]的所有LCS的部分,这时dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]。
另外注意负数取模。
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 int lcs[1111][1111],d[1111][1111]; 5 int main(){ 6 char s1[1111],s2[1111]; 7 int t; 8 scanf("%d",&t); 9 for(int cse=1; cse<=t; ++cse){ 10 scanf("%s%s",s1+1,s2+1); 11 int l1=strlen(s1+1),l2=strlen(s2+1); 12 for(int i=0; i<=l1; ++i) d[i][0]=1; 13 for(int i=0; i<=l2; ++i) d[0][i]=1; 14 for(int i=1; i<=l1; ++i){ 15 for(int j=1; j<=l2; ++j){ 16 if(s1[i]==s2[j]){ 17 lcs[i][j]=lcs[i-1][j-1]+1; 18 d[i][j]=d[i-1][j-1]; 19 }else if(lcs[i-1][j]==lcs[i][j-1]){ 20 lcs[i][j]=lcs[i-1][j]; 21 d[i][j]=(d[i-1][j]+d[i][j-1])%1000007; 22 if(lcs[i-1][j-1]==lcs[i-1][j]) d[i][j]=((d[i][j]-d[i-1][j-1])%1000007+1000007)%1000007; 23 }else if(lcs[i-1][j]>lcs[i][j-1]){ 24 lcs[i][j]=lcs[i-1][j]; 25 d[i][j]=d[i-1][j]; 26 }else{ 27 lcs[i][j]=lcs[i][j-1]; 28 d[i][j]=d[i][j-1]; 29 } 30 } 31 } 32 printf("Case %d: %d\n",cse,d[l1][l2]); 33 } 34 return 0; 35 }