题解转自:
首先这个题目肯定是按最长公共子序列的形式进行dp的,因为只有保证消去的一部分是最长公共子序列才能保证最后生成的序列最短。
因此,在记录方案数的时候我们也按最长公共子序列的生成过程来记录即可,我们不妨用p[i][j]记录最长公共子序列的字符数,用f[i][j]表示到第一个字符串第i个位置、第二个字符串第j个位置时生成的序列最短的方案种数。
当a[i]!=b[j]时,p[i][j]=max{p[i-1][j],p[i][j-1]},那么如果p[i][j]==p[i-1] [j],f[i][j]+=f[i-1][j],如果p[i][j]==p[i][j-1],f[i][j]+=f[i][j-1]。用文字翻译过来就是 说因为a[i]和b[j]是不同的,所以f[i][j]等于以a[i]结尾的最短的字符串的方案种数,加上以b[j]结尾的最短的字符串的方案种数。
当a[i]==b[j]时,p[i][j]=p[i-1][j-1]+1,f[i][j]+=f[i-1][j-1]。试想,我们这样算会不会少算某些部 分呢?因为毕竟也可以在a[i]和b[j]不配成一对的情况下生成最短的字符串呀。实际上,是可以证明f[i-1][j-1]包含了上述的情况的。譬如我 们假设b[j]和a[i]前面的某个字符配成一对,同时生成了最短的字符串,那么这个字符串必然是以a[i]结尾的某个最短字符串,而以a[i]结尾的所 有最短字符串的个数显然已经包含在f[i-1][j-1]之中了,因为f[i-1][j-1]本身就表示的是以a[i]为结尾的最短字符串的方案总数。
实际上,这个类似证明求最长公共子序列时如果a[i]==b[j],那么取p[i][j]=p[i-1][j-1]+1一定是最优的。
1 #include2 #include 3 #include 4 #include 5 using namespace std; 6 7 const int maxn = 40; 8 9 char s1[maxn], s2[maxn];10 int d[maxn][maxn];11 long long f[maxn][maxn];12 13 int main()14 {15 int T; scanf("%d", &T); getchar();16 17 for(int kase = 1; kase <= T; kase++)18 {19 gets(s1 + 1);20 gets(s2 + 1);21 int len1 = strlen(s1 + 1), len2 = strlen(s2 + 1);22 memset(d, 0x3f, sizeof(d));23 memset(f, 0, sizeof(f));24 for(int i = 0; i <= len1; i++) d[i][0] = 0, f[i][0] = 1;25 for(int i = 0; i <= len2; i++) d[0][i] = 0, f[0][i] = 1;26 27 for(int i = 1; i <= len1; i++)28 for(int j = 1; j <= len2; j++)29 {30 if(s1[i] == s2[j])31 {32 d[i][j] = d[i-1][j-1] + 1;33 f[i][j] = f[i-1][j-1];34 }35 else36 {37 d[i][j] = max(d[i][j-1], d[i-1][j]);38 if(d[i-1][j] == d[i][j]) f[i][j] += f[i-1][j];39 if(d[i][j-1] == d[i][j]) f[i][j] += f[i][j-1];40 }41 }42 43 printf("Case #%d: %d %lld\n", kase, len1 + len2 - d[len1][len2], f[len1][len2]);44 }45 46 return 0;47 }