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 } 

 

你可能感兴趣的:(LightOJ1157 LCS Revisited(DP))