LCS及方案数(DP)

Description

对于一个序列?[1], ?[2], … , ?[?],其子序列为一序列?[1], ?[2], … , ?[?],满 足1 ≤ ?[1] < ?[2] < ⋯ < ?[?] ≤ ?, 1 ≤ ? ≤ ?。?的两个子序列?, ?是不同的,当 且其长度不同,或存在一个位置?,满足?[?] ≠ ?[?]。

给定两个长度分别为?, ?的序列?, ?,定义序列二元组(?, ?)是好的,当且 仅当满足

  1. ?为?的子序列且?为?的子序列

  2. ?, ?长度相同

  3. 对于所有的1 ≤ ? ≤ ?的长度,满足?[?[?]] = ?[?[?]]

    两个二元组(?, ?), (?, ?)是不同的,当且仅当? ≠ ?或? ≠ ?。 现在希望你求出最大的?,满足存在二元组(?, ?)使得?的长度为?且(?, ?)是 好的。并求出满足长度为?的好的二元组的对数。答案对1e9+ + 7取模。

Input Format

包含两行字符串,分别表示序列?, ?。

Output Format

包含两行。 第一行为?。 第二个行为合法的二元组的对数对1e9 + 7取模的结果

Hint

对应100%的数据,?, ? ≤ 5000,保证序列只包含小写字母。

Solution

那么显然,这是一道动规题,在LCS的基础加上了求方案数

考试的时候只想出了求LCS,后面的方案数就完。。。全没想出来

关于LCS就一个数组f[i][j]表示A串前i个,B串前j个的LCS,

那么f[i][j]=max(f[i-1][j],f[i][j-1]),且当A[i]==B[i]时,f[i][j]=max(f[i-1][j-1]+1)

那么怎么求方案数呢?

首先设一个数组g[i][j]表示对于A串前i个,B串前j个的LCS的方案数,

那么就有,

f[i][j] = f[i - 1][j];
g[i][j] = g[i - 1][j];
if (f[i][j - 1] == f[i][j])
    g[i][j] += g[i][j - 1];
else if (f[i][j - 1] > f[i][j]) {
    f[i][j] = f[i][j - 1];
    g[i][j] = g[i][j - 1];
}
if (A[i - 1] == B[j - 1])
    if (f[i - 1][j - 1] + 1 > f[i][j]) {
        f[i][j] = f[i - 1][j - 1] + 1;
        g[i][j] = g[i - 1][j - 1];
    } else if (f[i - 1][j - 1] + 1 == f[i][j])
        g[i][j] += g[i - 1][j - 1];

其实也不难想到这样的转移,但只是这样会出问题,

比如A=“abcd",B="abef",g[4][4]会等于g[3][4]+g[4][3]=2,这显然是错误的,

我们发现,g[3][4]=g[3][3]+g[2][4],g[4][3]=g[3][3]+g[4][2],我们这里的g[3][3]是不必算两次,

也就是说这里的g[3][3]重复计算了一次,

但并不是每个转移都是这样,条件如下,

if (f[i - 1][j - 1] == f[i][j] && f[i - 1][j] == f[i][j - 1])
    g[i][j] -= g[i - 1][j - 1];

不再多说了,

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 5012
using namespace std;

const int yh = 1e9 + 7;
int n, m, f[N][N];
long long g[N][N];
char A[N], B[N];

int main()
{
    scanf("%s\n%s", A, B);
    n = strlen(A), m = strlen(B);
    for (int i = 0; i <= n; ++i) g[i][0] = 1;
    for (int i = 0; i <= m; ++i) g[0][i] = 1;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) {
            f[i][j] = f[i - 1][j];
            g[i][j] = g[i - 1][j];
            if (f[i][j - 1] == f[i][j])
                g[i][j] = (g[i][j] + g[i][j - 1]) % yh;
            else if (f[i][j - 1] > f[i][j]) {
                f[i][j] = f[i][j - 1];
                g[i][j] = g[i][j - 1];
            }
            if (A[i - 1] == B[j - 1]) {
                if (f[i - 1][j - 1] + 1 > f[i][j]) {
                    f[i][j] = f[i - 1][j - 1] + 1;
                    g[i][j] = g[i - 1][j - 1];
                } else if (f[i - 1][j - 1] + 1 == f[i][j])
                    g[i][j] += g[i - 1][j - 1] % yh;
            }
            if (f[i - 1][j - 1] == f[i][j] && f[i - 1][j] == f[i][j - 1])
                g[i][j] -= g[i - 1][j - 1];
        }
    printf("%d\n", f[n][m]);
    printf("%d\n", (g[n][m] % yh + yh) % yh);
    return 0;
}

转载于:https://www.cnblogs.com/void-f/p/7714396.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值