【学习笔记】子序列自动机

字符串算法是一点都不会啊qwq

基础

子序列自动机是用来解决一类字符串匹配问题的,特别之处在于它不是匹配子串而是子序列。

假设有一个长为\(n\)的字符串\(A\),如果有\(1 \leq p_1 <p_2\ ... < p_k \leq n\),令\(b_i = a_{p_i}\),则称字符串\(B\)=\(b_1b_2...b_k\)\(A\)的子序列。

所以子序列只需要按顺序,而不要求连续。

对于每一个位置\(i\),我们维护 \(to[i][c] , 0\leq i \leq n-1, c\in S\) ,其中\(S\)是字符集。(方便起见位置从\(1\)开始)

可以发现这个东西有点像字典树,但它里面存的是:位置\(i\)之后,字符\(c\)第一次出现的位置

举个例子:\(A="codeforces"\),那么\(to[1]['c']=8,to[4]['f']=5 ,to[3]['e']=4\)等等。

为什么只需要维护第一个位置?

很明显,如果我选择了更靠后的位置去匹配都能匹配上,那么对于这一位,去匹配靠前的肯定也行。(把'e'的蓝线改成红线肯定不会更差)。

所以我们只需要维护一个二维数组,按照匹配串\(B\)一位一位往后跳就行了。

数组的构建如下:(以只有英文小写字母为例)

for(i=1;i<=26;++i) to[n][i]=n+1;
    for(i=n-1;i>=0;--i){
        for(j=1;j<=26;++j) to[i][j]=to[i+1][j];
        to[n][a[i+1]-'a'+1]
    }

其中值为\(n+1\)表示合法的后继已经不存在了。

进阶

我们再来考虑一下字符集很大的情况\(|S|\leq 10^5\),这下暴力构建是\(O(n*|S|)\)的。

但是注意到每一个位置都是有它后一个递推来的,而实际改变的只有一位,绝大多数数据都是共用的,这就给优化创造了条件。

相信大佬们早就一眼秒出来了,对,就是主席树!

这东西不就是一个可持久化数组嘛OwO。

于是我们就可以做出下面这道模板辣!

例题

洛谷P5826 【模板】子序列自动机

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#define maxn 100010
#define maxL 1000010
using namespace std;
int a[maxn],b[maxL],type,n,m,q;
int l[maxn<<5],r[maxn<<5],lc[maxn<<5],rc[maxn<<5],val[maxn<<5],root[maxn],tot=0;
void read(int &x){
    x=0;
    char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9'){
        x=x*10+c-'0';
        c=getchar();
    }
}
void build(int x,int left,int right){
    l[x]=left,r[x]=right;
    if(left==right){
        val[x]=n+1;
        return;
    }
    int mid=left+right>>1;
    build(lc[x]=++tot,left,mid);
    build(rc[x]=++tot,mid+1,right);
}
void copy(int x,int y){
    lc[x]=lc[y],rc[x]=rc[y];
    l[x]=l[y],r[x]=r[y];
    val[x]=val[y];
}
void insert(int x,int y,int pos,int key){
    copy(x,y);
    if(l[x]==r[x]){val[x]=key;return;}
    int mid=l[y]+r[y]>>1;
    if(pos<=mid) insert(lc[x]=++tot,lc[y],pos,key);
    else insert(rc[x]=++tot,rc[y],pos,key);
}
int query(int x,int pos){
    if(l[x]==r[x]) return val[x];
    int mid=l[x]+r[x]>>1;
    if(pos<=mid) return query(lc[x],pos);
    else return query(rc[x],pos);
}
int main(){
    int i,j,L;
    read(type),read(n),read(q),read(m);
    build(root[n]=++tot,1,m);
    for(i=1;i<=n;++i) read(a[i]);
    for(i=n-1;i>=0;--i){
        insert(root[i]=++tot,root[i+1],a[i+1],i+1);
    }
    for(i=1;i<=q;++i){
        read(L);
        for(j=1;j<=L;++j) read(b[j]);
        int p=0,flag=1;
        for(j=1;j<=L;++j){
            p=query(root[p],b[j]);
            if(p>n){flag=0;break;}
        }
        if(flag) printf("Yes\n");
        else printf("No\n");
    }
    // system("pause");
    return 0;
}
posted @ 2022-11-08 15:41  文艺平衡树  阅读(164)  评论(0编辑  收藏  举报