最长公共子序列(洛谷P2516)

最长公共子序列(洛谷P2516)

题目大意

给定两个以.结尾的只含大写英文字母的字符串,求其最长公共子序列长度,以及构成最长公共子序列的方案数 (mod 1e8)|S|5000 (|S|)

题解

由最长公共子序列容易想到用dp动态规划进行求解,设fi,j为匹配了第一个字符串的前i个,第二个字符串的前j个的最长公共子序列。注:以下涉及第i个特指第一个字符串的第i个(即s1[i]),同理第j个(即s2[j])。

首先我不匹配第i个字符,转移方程为:

fi,j=max(fi,j,fi1,j)

不匹配第j个字符:

fi,j=max(fi,j,fi,j1)

匹配第i个字符和第j个字符,同时字符相等的情况下:

fi,j=max(fi,j,fi1,j1+1)

这样我们就得到了状态转移方程:

fi,j=max{0,i=0,j=0max(fi,j1,fi1,j)fi1,j1+1s1[i]==s2[j]

第一个问题解决了,然后就是方案数的问题了,方案数其实和长度的转移方程差不多,cnti,j表示构成fi,j最大长度的方案数,因为fi,j由三个地方转移而来,那么cnti,j也应由这些地方转移而来,需要注意的是,上述状态转移方程中fi1,j1会被转移到fi,j1fi1,j中,这在求长度中虽然不会影响答案,但是再求方案数时,我们会发现多计算了一次,那么在去重时,我们会发现,当且仅当fi1,j1等于fi,j时,重复计算的情况才会发生。

fi1,j1等于fi,j时,显然没有发生fi,j=max(fi,j,fi1,j1+1)的转移,但是其方案数和长度会转移到fi,j1fi1,j中,所以会多计算一次cnti1,j1的贡献。

fi1,j1不等于fi,j时,可以发现cnti1,j1的贡献永远不会转移到cnti,j中,也就不需要去重

然后就可以写代码了

#include<bits/stdc++.h>

using namespace std;

typedef long long ll ;
const int N = 5e5 + 5;
const ll mod = 1e8;
const ll inf = 1e9;

int dp[5050][5050];
int cnt[5050][5050];
char s1[5050], s2[5050];

int main() {

    scanf("%s%s", s1 + 1, s2 + 1);
    int n = strlen(s1 + 1) - 1, m = strlen(s2 + 1) - 1;
    for(int i = 0; i <= m; ++i)cnt[0][i] = 1;
    for(int i = 0; i <= n; ++i)cnt[i][0] = 1;
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= m; ++j){
            if(s1[i] == s2[j]){
                dp[i][j] = dp[i - 1][j - 1] + 1;
                cnt[i][j] = cnt[i - 1][j - 1];
            }
            if(dp[i][j] == dp[i - 1][j]){
                cnt[i][j] = (cnt[i][j] + cnt[i - 1][j]) % mod;
            }else if(dp[i][j] < dp[i - 1][j]){
                dp[i][j] = dp[i - 1][j];
                cnt[i][j] = cnt[i - 1][j];
            }

            if(dp[i][j] == dp[i][j - 1]){
                cnt[i][j] = (cnt[i][j] + cnt[i][j - 1]) % mod;
            }else if(dp[i][j] < dp[i][j - 1]){
                dp[i][j] = dp[i][j - 1];
                cnt[i][j] = cnt[i][j - 1];
            }

            if(dp[i - 1][j - 1] == dp[i][j]){
                cnt[i][j] = (cnt[i][j] - cnt[i - 1][j - 1] + mod) % mod;
            }
        }
    }
    cout << dp[n][m] << '\n' << cnt[n][m];
    return 0;
}

然而这样空间复杂度是(n2)的,会MLE,我们会发现这个转移第i位的转移只与第i1位有关,可以开一维空间数组记i1的状态,利用滚动数组优化第一维空间,空间复杂度就会变成(n)的了

#include<bits/stdc++.h>

using namespace std;

typedef long long ll ;
const int N = 5e5 + 5;
const ll mod = 1e8;
const ll inf = 1e9;

void read();

int dp[5050], cnt[5050];
int ldp[5050], lcnt[5050];//上一位的状态
char s1[5050], s2[5050];

int main() {

    scanf("%s%s", s1 + 1, s2 + 1);
    int n = strlen(s1 + 1) - 1, m = strlen(s2 + 1) - 1;
    for(int i = 0; i <= m; ++i)lcnt[i] = 1;
    cnt[0] = 1;
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= m; ++j){
            if(s1[i] == s2[j]){
                dp[j] = ldp[j - 1] + 1;
                cnt[j] = lcnt[j - 1];
            }
            
            if(dp[j] == ldp[j]){
                cnt[j] = (cnt[j] + lcnt[j]) % mod;
            }else if(dp[j] < ldp[j]){
                dp[j] = ldp[j];
                cnt[j] = lcnt[j];
            }

            if(dp[j] == dp[j - 1]){
                cnt[j] = (cnt[j] + cnt[j - 1]) % mod;
            }else if(dp[j] < dp[j - 1]){
                dp[j] = dp[j - 1];
                cnt[j] = cnt[j - 1];
            }

            if(ldp[j - 1] == dp[j]){    //去重
                cnt[j] = (cnt[j] - lcnt[j - 1] + mod) % mod;
            }
        }
        
        for(int j = 1; j <= m; ++j){
            ldp[j] = dp[j];
            lcnt[j] = cnt[j];
            dp[j] = cnt[j] = 0;
        }
    }
    cout << ldp[m] << '\n' << lcnt[m];
    return 0;
}


void read(){

#ifdef YYT
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif

}
posted @   言葉の庭  阅读(65)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示