所谓子序列自动机,就是根据子序列建立的自动机。
(逃)
前言
小清新算法。
解析
和其他自动机类似的,我们希望子序列自动机能且只能接受原串的所有子序列。
考虑一个问题:给你一个串 T,如何判断它是否是原串 S 的子串?
一个经典的贪心策略是:设当前在 T 的匹配位置是 i,S 的匹配位置是 j,那么就找到 S 的位置 j 之后第一个 T[i+1] 出现的位置 p,然后令 i=i+1,j=p,直到T全部匹配或S失配为止。
把类似的思想迁移到子序列自动机上,设 t o i , j to_{i,j} toi,j 表示 i 之后 第一个 j 出现的位置,匹配的时候不断跳 to 就行了,to数组可以通过倒着扫一边求解。
字符集较小时直接暴力为 to 数组赋值即可,字符集较大的时候考虑到 t o i to_i toi 和 t o i + 1 to_{i+1} toi+1 只有一位不同,所以使用可持久化线段树即可,复杂度可能要多一个 log。
代码
#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_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
read();
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;
}
/*
*/