「ICPC World Finals 2019 何以伊名始
本题现已知四种做法,如果不会后缀系列结构可以直接看Solution4
设初始树大小和查询总长均为(O(n))
Solution1
由于查询只有1e6,因此出现的不同查询串长度最多(O(sqrt {10^6})=1500)种
考虑对于每一种做一次dfs,在( ext{Hash Table})中查询,复杂度为(O(nsqrt n))
Solution2
将树上节点后缀排序,然后每次插入需要询问的字符就二分后缀区间
预处理复杂度为(O(nlog n)),查询涉及二分和倍增,复杂度为(O(nlog ^2 n))
Solution3
给定的树看做trie树,可以对于trie树建广义后缀自动机,然后倒着让询问串去匹配,一旦失配答案为0,
需要预处理(link)树的子树和,时间复杂度为(O(n)),空间复杂度为(O(n|Sigma|))
Solution4
将询问的串倒着插入,构建AC自动机
由于AC自动机预处理,时间空间复杂度为(O(n|Sigma|))
然后考虑对于树上每一个前缀在AC自动机上匹配,每次从父亲转移过来,复杂度为(O(n))
然后是常见的AC自动机操作,(fail)树上的子树累和即可
需要询问离线,因此有一定局限性
#include<bits/stdc++.h>
using namespace std;
#pragma GCC optimize(2)
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}
const int N=1e6+10;
int n,m,len,F[N],A[N];
char s[N],t[N];
int nxt[N][26],fail[N],cnt,pos[N];
int Q[N],L=1,R;
void Build(){
rep(i,0,25) if(nxt[0][i]) Q[++R]=nxt[0][i];
while(L<=R) {
int u=Q[L++];
rep(i,0,25) {
int &v=nxt[u][i];
if(v) fail[v]=nxt[fail[u]][i],Q[++R]=v;
else v=nxt[fail[u]][i];
}
}
}
int main(){
n=rd(),m=rd();
rep(i,1,n) scanf("%c",s+i),F[i]=rd();
rep(i,1,m) {
scanf("%s",t+1);
int now=0;
drep(j,strlen(t+1),1) {
int c=t[j]-'A';
if(!nxt[now][c]) nxt[now][c]=++cnt;
now=nxt[now][c];
}
pos[i]=now;
}
Build();
rep(i,1,n) A[F[i]=nxt[F[F[i]]][s[i]-'A']]++;
drep(i,R,1) A[fail[Q[i]]]+=A[Q[i]];
rep(i,1,m) printf("%d
",A[pos[i]]);
}