题意:假设两个字符串的最长公共子序列长度为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; }
HUD5282 Senior's String 详解(使用DP解决组合数学)
原文地址:http://blog.csdn.net/yanzheshi/article/details/47088059