字符串:序列自动机

序列自动机:

  • 原理:用一个数组nxt[i][j]表示字符串从i下标开始的最近的 c = j + 'a' 这个字符出现的位置
  • 举个例子:
    • s = "abcca" (默认下标为1-5)
    • nxt[0][0] = 1,即字符串中出现的第一个字符'a'的位置为1
    • nxt[1][1] = 2,即字符串距离下标1之后的字符'b'出现的第一个位置为2
    • 以此类推我们可以得到 nxt[1][0] = 5,nxt[1][2] = 3 等等

序列自动机的应用:

1、快速查找一个字符串是否是另一个字符串的子序列。
2、求解k个字符串的公共子序列个数。

例题1:

月月查华华的手机
直接求出序列自动机,多次查询即可。

点击查看折叠代码块
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn = 1e6+10;
int n;
char s[maxn];
char tmp[maxn];
int len;
int nxt[maxn][26];

void SeAM(){
    len = strlen(s+1);
    for (int i=len;i>=1;i--){
        for (int j=0;j<26;j++) nxt[i-1][j] = nxt[i][j];
        nxt[i-1][s[i]-'a'] = i;
    }
}

int main(){
    scanf("%s",s+1);
    SeAM();
    cin>>n;
    for (int i=1;i<=n;i++){
        scanf("%s",tmp+1);
        int l = strlen(tmp+1);
        int flag = 1;
        int pos = 0;
        for (int i=1;i<=l;i++){
            int p = nxt[pos][tmp[i]-'a'];
            if(!p){flag=0;break;}
            pos = p;
        }
        if(flag) puts("Yes");
        else puts("No");
    }
    return 0;
}


例题2:

P3856 [TJOI2008]公共子串
我们设 (dp[i][j][k]) 表示串a从i开始与串b从j开始与串c从k开始的公共子序列个数

则转移方程为 (dp[i][j][k] += dp[newi][newj][newk]), 当且仅当 (nxt1[i][c]) && (nxt2[j][c]) && (nxt3[k][c]) 三个值同时不为0时成立, 0<=c<=25
(newi = nxt1[i][c])
(newj = nxt2[j][c])
(newk = nxt3[k][c])

特别的,如果 i && j && k,(dp[i][j][k]+=1)

点击查看折叠代码块
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e3+10;
int n;
char s1[maxn],s2[maxn],s3[maxn];
int nxt1[maxn][26],nxt2[maxn][26],nxt3[maxn][26];
int l1,l2,l3;
void init(){
    l1 = strlen(s1+1);
    for (int i=l1;i>=1;i--){
        for (int j=0;j<26;j++){
            nxt1[i-1][j] = nxt1[i][j];
        }
        nxt1[i-1][s1[i]-'a'] = i;
    }

    l2 = strlen(s2+1);
    for (int i=l2;i>=1;i--){
        for (int j=0;j<26;j++){
            nxt2[i-1][j] = nxt2[i][j];
        }
        nxt2[i-1][s2[i]-'a'] = i;
    }

    l3 = strlen(s3+1);
    for (int i=l3;i>=1;i--){
        for (int j=0;j<26;j++){
            nxt3[i-1][j] = nxt3[i][j];
        }
        nxt3[i-1][s3[i]-'a'] = i;
    }
}

ll dp[200][200][200];


ll dfs(int p1,int p2,int p3){
    if(dp[p1][p2][p3]) return dp[p1][p2][p3];
    for (int i=0;i<26;i++){
        if(nxt1[p1][i] && nxt2[p2][i] && nxt3[p3][i]){
            dp[p1][p2][p3] += dfs(nxt1[p1][i],nxt2[p2][i],nxt3[p3][i]);
        }
    }
    if(p1 && p2 && p3) dp[p1][p2][p3]++;
    return dp[p1][p2][p3];
}
int main(){
    scanf("%s%s%s",s1+1,s2+1,s3+1);
    init();
    ll ans = dfs(0,0,0);
    cout<<ans;
    return 0;
}


例题3:


我们固定左端点l,然后枚举c = 'a'-'z'每个字符在l后出现的第一个位置为p,则以l为左端点,p到n为右端点的所有的子串都对答案有1的贡献

点击查看折叠代码块
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
int n;
char s[maxn];
int nxt[maxn][26];

void init(){
    for (int i=n;i>=1;i--){
        for (int j=0;j<26;j++) nxt[i-1][j] = nxt[i][j];
        nxt[i-1][s[i]-'a'] = i;
    }
}
int main(){
    cin>>(s+1);
    n = strlen(s+1);
    init();
    ll ans = 0;
    for (int i=1;i<=n;i++){
        for (int j=0;j<26;j++){
            int p = nxt[i-1][j];
            if(!p) continue;
            ans += (n-p+1);
        }
    }
    cout<<ans<<endl;
    return 0;
}
你将不再是道具,而是成为人如其名的人
原文地址:https://www.cnblogs.com/wsl-lld/p/14639052.html