子序列自动机 学习笔记

定义

子序列自动机是接受且只接受一个串的子序列的自动机。

子序列自动机有 \(n+1\) 个状态,也就是主串的下标加上 \(0\),起始状态为 \(0\)。令自动机的转移函数为 \(to_{i,c}\),则 \(to_{i,c}\)\(i\) 之后第一个字符 \(c\) 的位置(第一个是因为这样能匹配完更多的子序列,否则可能出现字符 \(c\) 恰好用完的情况)。

一个简单的建立方式是倒序的扫一遍,在这期间维护每个字符上一次出现的位置。

int l[maxn];
for(int i=0;i<maxn;i++)l[i]=to[n+1][i]=n+1;
for(int i=n;i>=0;i--){
  for(int j=0;j<maxn;j++)to[i][j]=l[j];
  l[to_num(s[i])]=i;
}

这样的复杂度是 \(O(n|\Sigma|)\) 的。当字符集很大,比如一个数列,可以先把每个字符的出现位置按顺序存起来,需要时再二分,代价是查询为 \(O(\log n)\)

vector<int>p[maxn];
void build(int n,int m,int s[]){
  for(int i=1;i<=n;i++)p[s[i]].push_back(i);
  for(int i=1;i<=m;i++)p[i].push_back(n+1);
}
int get(int x,int c){
  return *upper_bound(p[c].begin(),p[c].end(),x);
}

子序列自动机可以解决一些子序列的问题,比如有多个模式串,判断模式串是否为主串的子序列。建立主串的子序列自动机,查询时在自动机上跳即可做到 \(O(\sum m)\)\(O(\log n\sum m)\)

P5826

#include<bits/stdc++.h>
using namespace std;
char buf1[2097152],*ip1=buf1,*ip2=buf1;
inline int getc(){
  return ip1==ip2&&(ip2=(ip1=buf1)+fread(buf1,1,2097152,stdin),ip1==ip2)?EOF:*ip1++;
}
template<typename T>void in(T &a)
{
  T ans=0;
  char c=getc();
  for(;c<'0'||c>'9';c=getc());
  for(;c>='0'&&c<='9';c=getc())ans=ans*10+c-'0';
  a=ans;
}
template<typename T,typename... Args>void in(T &a,Args&...args)
{
  in(a),in(args...);
}
int type,n,q,m,a[100005],l,b[1000005];
template<int maxn>struct SQAM{
  vector<int>p[maxn];
  void build(int n,int m,int s[]){
    for(int i=1;i<=n;i++)p[s[i]].push_back(i);
    for(int i=1;i<=m;i++)p[i].push_back(n+1);
  }
  int get(int x,int c){
    return *upper_bound(p[c].begin(),p[c].end(),x);
  }
  bool query(int n,int m,int t[]){
    for(int i=1,pos=0;i<=m;i++){
      pos=get(pos,t[i]);
      if(pos>n)return 0;
    }
    return 1;
  }
};
SQAM<100005>t;
int main(){
  in(type,n,q,m);
  for(int i=1;i<=n;i++)in(a[i]);
  t.build(n,m,a);
  for(int i=1;i<=q;i++){
    in(l);
    for(int j=1;j<=l;j++)in(b[j]);
    puts(t.query(n,l,b)?"Yes":"No");
  }
  return 0;
}

例题

P1819

建出三个串的子序列自动机,在子序列自动机上 DP。设三个自动机的转移分别为 \(to1_{i,c},to2_{j,c},to3_{k,c}\)。设 \(f_{i,j,k}\) 为从三个串的第 \(i,j,k\) 个字符开始的公共子序列个数,则 \(f_{i,j,k}=1+\sum f_{to1_{i,c},to2_{j,c},to3_{k,c}}\)。答案为 \(f_{0,0,0}-1\)

#include<bits/stdc++.h>
using namespace std;
const int mod=100000000;
int n,f[155][155][155];
char a[155],b[155],c[155];
template<int maxn1,int maxn2>struct SQAM{
  int to[maxn1][maxn2];
  void build(int n,char s[]){
    int l[maxn2];
    for(int i=0;i<maxn2;i++)l[i]=n+1;
    for(int i=n;i>=0;i--){
      for(int j=0;j<maxn2;j++)to[i][j]=l[j];
      l[s[i]-'a']=i;
    }
  }
};
SQAM<155,26>t1,t2,t3;
int main(){
  cin>>n>>a+1>>b+1>>c+1,t1.build(n,a),t2.build(n,b),t3.build(n,c);
  for(int i=n;i>=0;i--){
    for(int j=n;j>=0;j--){
      for(int k=n;k>=0;k--){
        f[i][j][k]=1;
        for(int c=0;c<26;c++){
          f[i][j][k]=(f[i][j][k]+f[t1.to[i][c]][t2.to[j][c]][t3.to[k][c]])%mod;
        }
      }
    }
  }
  return cout<<(f[0][0][0]-1+mod)%mod<<'\n',0;
}

[[字符串]]

posted @ 2024-03-01 09:38  lgh_2009  阅读(26)  评论(0编辑  收藏  举报