Loading

2022ICPC网络赛 L LCS-like Problem(DP 子序列自动机)

L LCS-like Problem(DP 子序列自动机)

题目:

​ 给出两个串s, t。请找出一个最长的子序列\(s'\),使其与\(t\)的最长公共子序列长度不大于1。输出这个最长的长度。

思路:

​ 题目名字是LCS,且题意比较符合DP的定义,优先考虑DP而非字符串来求解问题。

​ 题目要求在s中找一个最长的满足题意的子序列。那么我们的状态表示一定是跟s有关的,又可以知道本题的转移一定是跟字符存在关系的。根据经验,可以定义出\(f[i][j]\)表示在s的前i个字符中选择,且最后一个在t中出现的字符为j(这里是由后面转移时推出)的最大长度。

​ 转移的时候,对于\(s[i]\)的两种情况我们需要分别讨论。一:当\(s[i]\)不在\(t\)中的时候,那么他可以跟在任何字符的后面。二:当\(s[i]\)存在于\(t\)中的时候,我们要根据是否能接成一个长度大于1的子序列来判断是否能进行转移。

实现:

​ 这里解答两个点,其一是对于f数组的定义。由于动态规划的无后效性,我们可以知道我们每次转移的时候只需要看最后一个字符就可以了。但是有一个问题,当最后一个字符\(s[i]\)不在\(t\)的时候,如果定义为最后一个字符,就会覆盖掉上一个正在限制后面无效状态的状态。所以我们定义的是,最后一个在t中的字符

​ 其二是关于序列自动机这个东西。其实这个知识点还挺复杂的,详情可以观看下方链接。不过本题主要只是运用其思想,本题自动机状态非常简化,只需要保存字符\(j\)是否能在字符\(i\)的后面出现来辅助转移就可以完成。

https://blog.csdn.net/Berserker____/article/details/109062238

#include <bits/stdc++.h>

using namespace std;

const int N = 500005;
char s[N], t[N];
int n, m;
int vis[26]; //t中是否已经含有i
int ban[26][26]; //i的后面不能出现j 因为是子序列
int f[N][26]; //从s的前i个字符选出的子序列,且最后一个在t中出现的字符为j的最大长度

void init() //子序列自动机预处理
{
    for(int i = m; i >= 1; i --)
    {
        for(int j = 0; j < 26; j ++)
            if(vis[j])  ban[t[i]][j] = 1;
        vis[t[i]] = 1;
    }
}

int main()
{
    scanf("%s", s + 1);
    scanf("%s", t + 1);
    n = strlen(s + 1);
    m = strlen(t + 1);

    for(int i = 1; i <= n; i ++)    s[i] -= 'a';
    for(int i = 1; i <= m; i ++)    t[i] -= 'a';

    init();

    for(int i = 1; i <= n; i ++)
    {
        for(int j = 0; j < 26; j ++)
            f[i][j] = f[i - 1][j] + (vis[s[i]] == 0); //如果s[i]没有在t中出现,可以直接接在后面,且不影响末尾
        for(int j = 0; j < 26; j ++)
            if(!ban[j][s[i]] && vis[s[i]]) //如果存在这个数,那么不能接在j的后面
                f[i][s[i]] = max(f[i][s[i]], f[i - 1][j] + 1);
    }

    int res = 0;
    for(int i = 0; i < 26; i ++)
        res = max(res, f[n][i]);
    cout << res << '\n';
}
posted @ 2022-09-18 21:49  DM11  阅读(286)  评论(0编辑  收藏  举报