【学习笔记】子序列自动机
字符串算法是一点都不会啊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。
于是我们就可以做出下面这道模板辣!
例题
代码
点击查看代码
#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;
}