何以伊名始

本题现已知四种做法,如果不会后缀系列结构可以直接看Solution4

设初始树大小和查询总长均为$O(n)$

Solution1

由于查询只有1e6,因此出现的不同查询串长度最多$\sqrt {10^6}=1000$种

考虑对于每一种做一次dfs,在$\text{Hash Table}$中查询,复杂度为$O(n\sqrt n)$

Solution2

将树上节点后缀排序,然后每次插入需要询问的字符就二分后缀区间

预处理复杂度为$O(n\log n)$,查询涉及二分和倍增,复杂度为$O(n\log ^2 n)$

Solution3

给定的树看做trie树,可以对于trie树建广义后缀自动机,然后倒着让询问串去匹配,一旦失配答案为0,

需要预处理$link$树的子树和,时间复杂度为$O(n)$,空间复杂度为$O(n|\Sigma|)$

Solution4

将询问的串倒着插入,构建AC自动机

由于AC自动机预处理,时间空间复杂度为$O(n|\Sigma|)$

然后考虑对于树上每一个前缀在AC自动机上匹配,每次从父亲转移过来,复杂度为$O(n)$

然后是常见的AC自动机操作,$fail$树上的子树累和即可

需要询问离线,因此有一定局限性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#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\n",A[pos[i]]);
}