题意:假设两个字符串的最长公共子序列长度为L,求第一个字符串中有多少个长度为L的子序列是第二个字符串的子序列。显然找出一个字符串的所有长度为L的子序列是组合数学问题,如果枚举所有子串的时间复杂度是n! 级的。这里就需要用动态规划来解决。首先用dp[i][j]和num[i][j]分别记录x的前I个字母和y的前j 个字母的最长公共子序列的长度和个数。先求出dp, 然后求num:。求num[i][j]分为两种情况,子序列不选x[i]和选x[i]:
1. 不选x[i]: 如果dp[i][j] == dp[i-1][j] 说明不选x[i]也能达到dp[i][j],问题转换到num[i-1][j]。
2. 选x[i]: 如果用p = pre[j][x[i] - 'a']表示在字符串y的前j个元素中等于x[i]且最接近j的位置,显然x[i] = y[p]。既然选了x[i], 那么必须从x的前i-1个字符中选择出长度等于dp[i][j] - 1的同时是y的前p-1个字符组成的序列的子序列,这样再加上x[i]就组成了满足条件的长度为dp[i][j]且结尾是x[i]的一个子序列。所以转换到num[i-1][p-1]。
每个num[i][j]都是上面两个条件得到结果的和。注意两种情况都要条件限制,这样才能做到不重复不遗漏。
代码如下:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 1010, MOD = 1e9+7;
char x[MAXN], y[MAXN];
int dp[MAXN][MAXN], num[MAXN][MAXN], pre[MAXN][26];
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf(" %s%s", x+1, y+1);
int i, j, m = strlen(x+1), n = strlen(y+1);
memset(pre, 0, sizeof(pre));
for(i = 1; i <= n; i++)
{
for(j = 0; j < 26; j++)
pre[i][j] = pre[i-1][j]; //用来记录前y的前i个字符中每个字符最靠后的位置
pre[i][y[i] - 'a'] = i;
}
memset(num, 0, sizeof(num));
for(i = 0; i <= m; i++)
{
for(j = 0; j <= n; j++)
{
if(i == 0 || j == 0)
dp[i][j] = 0;
else
{
if(x[i] == y[j])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
//处理num
if(dp[i][j] == 0)
num[i][j] = 1;
else
{
if(dp[i-1][j] == dp[i][j]) //第一种条件
num[i][j] = num[i-1][j];
int p = pre[j][x[i]-'a']; //第二种条件
if(p && dp[i-1][p-1] + 1 == dp[i][j])
num[i][j] = (num[i-1][p-1] + num[i][j]) % MOD;
}
}
}
printf("%d\n", num[m][n]);
}
return 0;
}