[HAOI2010][DP]最长公共子序列

[Problem Description]
字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij = yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。
对给定的两个字符序列,求出他们最长的公共子序列长度,以及最长公共子序列个数。
[Algorithm]
DP 容斥
[Analysis]
最长公共子序列是一个经典的dp模型。但是加上统计个数就比较麻烦。我们可以很容易的想到这样的递推式
when s[i] == s[j]
f[i][j] = f[i - 1][j - 1] + 1
way[i][j] = way[i - 1][j - 1]
way[i][j] += way[i][j - 1] (f[i][j] == f[i][j - 1)
way[i][j] += way[i - 1][j] (f[i][j] == f[i - 1][j])
when s[i] != s[j]
f[i][j] = max(f[i - 1][j], f[i][j - 1])
way[i][j] = 0;
way[i][j] += way[i][j - 1](f[i][j] == f[i][j - 1])
way[i][j] += way[i - 1][j](f[i][j] == f[i - 1][j])
第一种情况是没有问题的,但第二种情况有问题。如果f[i][j - 1]和f[i - 1][j]都是由f[i - 1][j - 1]推出来的,那么way[i][j]里面相当于加了两遍的way[i - 1][j - 1],在这种情况下要加上way[i - 1][j - 1],这样就没有问题了。
[Pay Attention]
想了半天没有想到会有重复的情况,还是看了别人的题解才知道的……所以对于这种拿不准的转移要多想想,实在不行多出几组数据或者写个暴力对拍……还有一个蛋疼的地方,题目要求mod的数是1e8,我看成了1e9……要小心
[Code]
/**************************************************************
    Problem: 2423
    User: gaotianyu1350
    Language: C++
    Result: Accepted
    Time:1668 ms
    Memory:1352 kb
****************************************************************/
 
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
using namespace std;
 
const int MOD = (int)1e8;
const int MAXN = 5010;
 
int f[2][MAXN] = {{0}}, way[2][MAXN] = {{0}};
string s1, s2;
 
inline void update(int &maxLen, int &maxWay, int len, int way)
{
    if (len > maxLen)
    {
        maxLen = len;
        maxWay = way;
    }
    else if (len == maxLen)
        maxWay = (maxWay + way) % MOD;
}
 
int main()
{
    //freopen("input.txt", "r", stdin);
    int pre = 0, now = 1;
    getline(cin, s1, '.');
    getchar();
    getline(cin, s2, '.');
    int len1 = s1.length(), len2 = s2.length();
    for (int i = 0; i <= len2; i++)
        way[pre][i] = 1;
    way[now][0] = 1;
    for (int i = 1; i <= len1; i++)
    {
        for (int j = 1; j <= len2; j++)
        {
            f[now][j] = f[now][j - 1];
            way[now][j] = way[now][j - 1];
            update(f[now][j], way[now][j], f[pre][j], way[pre][j]);
            if (s1[i - 1] == s2[j - 1])
                update(f[now][j], way[now][j], f[pre][j - 1] + 1, way[pre][j - 1]);
            if (s1[i - 1] != s2[j - 1] && f[now][j - 1] == f[pre][j] && f[pre][j - 1] == f[now][j])
                way[now][j] = (way[now][j] + MOD - way[pre][j - 1]) % MOD;
        }
        pre ^= 1;
        now ^= 1;
    }
    printf("%d\n%d\n", f[pre][len2], way[pre][len2]);
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值