所谓子序列自动机,就是根据子序列建立的自动机。
(逃)
前言
小清新算法。
解析
和其他自动机类似的,我们希望子序列自动机能且只能接受原串的所有子序列。
考虑一个问题:给你一个串 T,如何判断它是否是原串 S 的子串?
一个经典的贪心策略是:设当前在 T 的匹配位置是 i,S 的匹配位置是 j,那么就找到 S 的位置 j 之后第一个 T[i+1] 出现的位置 p,然后令 i=i+1,j=p,直到T全部匹配或S失配为止。
把类似的思想迁移到子序列自动机上,设 toi,jto_{i,j}toi,j 表示 i 之后 第一个 j 出现的位置,匹配的时候不断跳 to 就行了,to数组可以通过倒着扫一边求解。
字符集较小时直接暴力为 to 数组赋值即可,字符集较大的时候考虑到 toito_itoi 和 toi+1to_{i+1}toi+1 只有一位不同,所以使用可持久化线段树即可,复杂度可能要多一个 log。
代码
P5826 【模板】子序列自动机
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ULL unsigned ll
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
inline ll read() {ll x(0),f(1);char c=getchar();while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}
const int N=1e5+100;
const int mod=998244353;
const ll inf=1e9;bool mem1;bool Flag=0;int n,m,q;struct node{int ls,rs,w;
}tr[N*20];
int rt[N],tot;
inline int copy(int x){tr[++tot]=tr[x];return tot;
}
#define mid ((l+r)>>1)
void upd(int &k,int l,int r,int p,int w){k=copy(k);if(l==r){tr[k].w=w;return;}if(p<=mid) upd(tr[k].ls,l,mid,p,w);else upd(tr[k].rs,mid+1,r,p,w);//printf(" k=%d (%d %d) ls=%d rs=%d\n",k,l,r,tr[k].ls,tr[k].rs);
}
int ask(int k,int l,int r,int p){//printf(" k=%d (%d %d) ls=%d rs=%d\n",k,l,r,tr[k].ls,tr[k].rs);if(!k) return n+1;if(l==r) return tr[k].w;if(p<=mid) return ask(tr[k].ls,l,mid,p);else return ask(tr[k].rs,mid+1,r,p);
}
int a[N];int main(){#ifndef ONLINE_JUDGEfreopen("a.in","r",stdin);freopen("a.out","w",stdout);#endifread();n=read();q=read();m=read();tr[0].w=n+1;for(int i=1;i<=n;i++) a[i]=read();for(int i=n-1;i>=0;i--){//printf("i=%d\n",i);rt[i]=rt[i+1];upd(rt[i],1,m,a[i+1],i+1);}for(int t=1;t<=q;t++){int len=read();for(int i=1;i<=len;i++) a[i]=read();int p=0;for(int i=1;i<=len&&p<=n;i++){p=ask(rt[p],1,m,a[i]);//printf(" i=%d a=%d p=%d\n",i,a[i],p);}if(p<=n) puts("Yes");else puts("No");}return 0;
}
/*
*/