序列自动机
引入
给定一个长度为 \(n\) 的正整数序列 \(a\) ,有 \(q\) 次询问,第 \(i\) 次询问给定一个长度为 \(L_i\) 的序列 \(b_i\),请你判断 \(b_i\) 是不是 \(a\) 的子序列。序列 \(a\) 和所有 \(b_i\) 中的元素都不大于一个给定的正整数 \(m\)。
\(1 \leq n, m, q \leq 10^5\),\(1 \leq a_i, b_{i, j} \leq m\),\(1 \leq l_i \leq 10^6\),\(\sum_{i = 1}^{q} l_i \leq 10^6\)。
做法
考虑维护一个能够维护某个串的所有本质不同的子序列的自动机。
序列自动机
可能是最简单构造/理解的,附加内容最少的自动机。
构造
考虑贪心。对于一个串 \(s\),从某个位置开始维护子序列时,显然让每个字符均在 \(s\) 尽可能靠前的位置出现时,后面能够接的子序列数会尽量多。这样不会减少能够构造的数量的子序列数量。
设 \(son(i,c)\) 表示 \(s_i\) 之后第一个字符 \(c\) 出现的位置。将 \(son(i,c)\) 抽象成转移边,则所有的 \(son\) 转移边构成了自动机。而 \(son\) 的求法也很简单,逆推即可:
字符集较大时,可以用主席树维护转移边。
模板代码
点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxl=18;
const int maxn=1000010;
int n,m,q,i,lt,rt,mt,pt,qt,tot;
int a[maxn];
struct node{
int ls,rs,val;
}tr[maxn*maxl];
#define ls(p) tr[p].ls
#define rs(p) tr[p].rs
#define val(p) tr[p].val
int root[maxn];
int main(){
scanf("%d",&n);
scanf("%d%d%d",&n,&q,&m);
for(i=1;i<=n;++i) scanf("%d",a+i);
root[n]=tot=1;
for(i=n;i;--i){
pt=root[i-1]=++tot;
lt=1;rt=m;qt=root[i];
while(lt<rt){
tr[pt]=tr[qt];
mt=(lt+rt)>>1;
if(a[i]<=mt){
ls(pt)=++tot;
rt=mt;qt=ls(qt);
}
else{
rs(pt)=++tot;
lt=mt+1;qt=rs(qt);
}
pt=tot;
}
val(pt)=i;
}
while(q--){
scanf("%d",&n);
for(i=1;i<=n;++i) scanf("%d",a+i);
pt=root[0];
for(i=1;i<=n;++i){
lt=1;rt=m;
while(lt<rt){
mt=(lt+rt)>>1;
if(a[i]<=mt){
pt=ls(pt);
rt=mt;
}
else{
pt=rs(pt);
lt=mt+1;
}
}
pt=val(pt);
if(!pt){
printf("No\n");
break;
}
else pt=root[pt];
}
if(pt) printf("Yes\n");
}
return 0;
}
例题:
给出字符串 \(S\),计算其本质不同的子序列数,答案取模。\(|S|\le 3\times 10^5\)。
附加内容 1:无。
直接求自动机上不同路径数即可。
附加内容 2:求其 \(k\) 小子序列。\(k\le 10^{18}\)。
附加内容 2.5:\(k\le 10^9,|S|\le 10^6\)。
在子序列自动机的 DAG 上找出从某个点出发的路径条数(根节点之外的点的路径条数要加一),大于 \(k\) 的直接赋为 \(\inf\)(打 \(\inf\) 标记),然后模拟 dfs 过程,按位填字符即可,方法可以参照 P3975 [TJOI2015]弦论 的做法。注意原题空间只有 125MB,故在 DAG 上 dp 找路径条数时,不应该建反图然后拓扑 dp,而是 dfs 以递归查找。
代码
点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000010;
const int maxx=1000000000;
const int Inf=1000000001;
int n,k,i,j,l,r,u,v,tot;
int num[maxn],nxt[maxn][26];
char s[maxn];
bool vis[maxn];
void dfs(int p){
if(vis[p]) return;
vis[p]=1;
int lp,to;
for(lp=0;lp<26;++lp){
to=nxt[p][lp];
if(!to) continue;
dfs(to);
num[p]+=num[to];
if(num[p]>maxx){
num[p]=Inf;
return;
}
}
}
int main(){
scanf("%d%d%s",&n,&k,s+1);
for(i=n;i;--i){
num[i]=1;
for(j=25;j>=0;--j) nxt[i-1][j]=nxt[i][j];
nxt[i-1][s[i]-'a']=i;
}
dfs(0);
++k;
for(;;){
if(k==1) return 0;
--k;
for(j=0;j<26;++j){
v=nxt[u][j];
if(!v) continue;
if(k<=num[v]){
u=v;
putchar(j+'a');
break;
}
else k-=num[v];
}
}
return 0;
}
附加内容3:\(q\) 组询问,每次询问第 \(k\) 小子序列的最后 \(r\) 个字符。\(k\le 10^{18},q\le 10^5,\sum r\le 10^6\)。
待补。
本文来自博客园,作者:Fran-Cen,转载请注明原文链接:https://www.cnblogs.com/Fran-CENSORED-Cwoi/p/16775656.html