HUD5282 Senior's String 详解(使用DP解决组合数学)

题意:假设两个字符串的最长公共子序列长度为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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值