这里给出一个后缀自动机的做法.
假设每次询问 $t$ 在所有 $s$ 中的出现次数,那么这是非常简单的:
直接对 $s$ 构建后缀自动机,随便维护一下 $endpos$ 大小就可以.
然而,想求 $t$ 在 $trie$ 树中一个节点到根的字符串中的出现次数就难了很多.
我们慢慢讲:
首先,我们对题中给的 $trie$ 树(即所有 $s$ 串)构建广义后缀自动机.
因为后缀自动机能识别所有的子串,所以可以直接将 $t$ 在自动机上匹配.
假设匹配成功,即得到终止节点 $p$.
那么我们想求 $s_{i}$ 中包含 $p$ 的个数.
令 $s_{i}$ 对应到 $trie$ 树的终止节点为 $end(i)$ ,那么将 $end(i)$ 到根节点的所有字符都插入后缀自动机之后,对答案有贡献的就是 $trie$ 中 $end(i)$ 到根中后缀包含 $t$ 的节点个数.
而巧妙的是,对 $s_{i}$ 有贡献的所有 $trie$ 中的节点之间的位置关系是链的关系.
即他们都在 $end(i)$ 到根节点这条路径上.
于是,我们就联想,什么数据结构,能让深度递增的节点编号连续呢?
——树链剖分.
没错,我们对 $trie$ 来一遍树剖求解每个点的树剖序.
对后缀自动机每一个节点建一个线段树,维护的是后缀树中该节点为根子树中所有树剖序的所有标号种类.
对于这个,我们可以在扩展 $trie$ 树字符的时候就在自动机中新建节点插入该点的树剖序,然后扩展完 $trie$ 树后再来一遍线段树合并.
最后,只需暴力跳重链,并查询 $dfn[top[x]]$~$dfn[x]$ 在 $p$ 节点所在线段树中有几个出现.
时间复杂度为 $O(nlog^2n)$,常数巨大,远没有 AC 自动机好写,运行快.
不过,这确实是一道练习后缀自动机的好题.
#include <map>
#include <vector>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 400003
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
char S[N];
int n,m,rt[N<<1];
namespace Trie {
int id[N],tim;
struct Node {
map<int,int>ch;
int siz,dfn,top,son,fa;
}t[N];
void dfs1(int u) {
t[u].siz=1;
for(int i=0;i<27;++i)
if(t[u].ch.count(i)) {
int v=t[u].ch[i];
t[v].fa=u,dfs1(v),t[u].siz+=t[v].siz;
if(!t[u].son||t[v].siz>t[t[u].son].siz) t[u].son=v;
}
}
void dfs2(int u,int tp) {
t[u].top=tp;
t[u].dfn=++tim;
if(t[u].son) dfs2(t[u].son,tp);
for(int i=0;i<27;++i)
if(t[u].ch.count(i)) {
int v=t[u].ch[i];
if(v!=t[u].son)
dfs2(v,v);
}
}
void build_tree() {
dfs1(0), dfs2(0,0);
}
};
namespace seg {
#define ls t[x].lson
#define rs t[x].rson
int tot;
struct Node {
int lson,rson,sum;
}t[N*50];
void pushup(int x) {
t[x].sum=t[ls].sum+t[rs].sum;
}
void update(int &x,int l,int r,int p,int v) {
if(!x)
x=++tot;
if(l==r) {
t[x].sum=v;
return;
}
int mid=(l+r)>>1;
if(p<=mid) update(ls,l,mid,p,v);
else update(rs,mid+1,r,p,v);
pushup(x);
}
int query(int x,int l,int r,int L,int R) {
if(!x) return 0;
if(l>=L&&r<=R) return t[x].sum;
int mid=(l+r)>>1,re=0;
if(L<=mid) re+=query(ls,l,mid,L,R);
if(R>mid) re+=query(rs,mid+1,r,L,R);
return re;
}
int merge(int l,int r,int x,int y,int tt) {
if(!x||!y)
return x+y;
int oo=++tot;
if(l==r)
t[oo].sum=1;
else {
int mid=(l+r)>>1;
t[oo].lson=merge(l,mid,t[x].lson,t[y].lson,tt);
t[oo].rson=merge(mid+1,r,t[x].rson,t[y].rson,tt);
pushup(oo);
}
return oo;
}
#undef ls
#undef rs
};
namespace SAM {
int tot,id[N<<1],tax[N<<1],rk[N<<1];
struct Node {
map<int,int>ch;
int len,pre;
}t[N<<1];
struct Edge {
int from,c;
Edge(int from=0,int c=0):from(from),c(c){}
};
queue<int>q;
int extend(int lst,int c,int i) {
int p=lst;
if(t[p].ch.count(c)) {
int q=t[p].ch[c];
if(t[q].len==t[p].len+1) seg::update(rt[q],1,Trie::tim,i,1),lst=q;
else {
int nq=++tot;
t[nq].len=t[p].len+1;
t[nq].pre=t[q].pre,t[q].pre=nq;
t[nq].ch=t[q].ch;
seg::update(rt[nq],1,Trie::tim,i,1);
for(;p&&t[p].ch.count(c)&&t[p].ch[c]==q;p=t[p].pre) t[p].ch[c]=nq;
lst=nq;
}
}
else {
int np=++tot;
t[np].len=t[p].len+1;
seg::update(rt[np],1,Trie::tim,i,1);
for(;p&&!t[p].ch.count(c);p=t[p].pre) t[p].ch[c]=np;
if(!p) t[np].pre=1;
else {
int q=t[p].ch[c];
if(t[q].len==t[p].len+1) t[np].pre=q;
else {
int nq=++tot;
t[nq].len=t[p].len+1;
t[nq].pre=t[q].pre,t[q].pre=t[np].pre=nq;
t[nq].ch=t[q].ch;
for(;p&&t[p].ch.count(c)&&t[p].ch[c]==q;p=t[p].pre) t[p].ch[c]=nq;
}
}
lst=np;
}
return lst;
}
void construct() {
int i,j;
id[0]=tot=1;
q.push(0);
while(!q.empty()) {
int u=q.front();q.pop();
for(i=0;i<27;++i) {
if(Trie::t[u].ch.count(i)) {
int v=Trie::t[u].ch[i];
q.push(v);
id[v]=extend(id[u],i,Trie::t[v].dfn);
}
}
}
for(i=1;i<=tot;++i) tax[i]=0;
for(i=1;i<=tot;++i) ++tax[t[i].len];
for(i=1;i<=tot;++i) tax[i]+=tax[i-1];
for(i=1;i<=tot;++i) rk[tax[t[i].len]--]=i;
for(i=tot;i>1;--i) {
int u=rk[i];
int ff=t[u].pre;
if(ff>1)
rt[ff]=seg::merge(1,Trie::tim,rt[u],rt[ff],0);
}
}
};
void solve(int x) {
int p=1,i,len=strlen(S+1),re=0;
for(i=1;i<=len;++i) {
int c=S[i]-'a';
if(SAM::t[p].ch.count(c))
p=SAM::t[p].ch[c];
else {
printf("0\n");
return;
}
}
for(x=Trie::id[x];x;x=Trie::t[Trie::t[x].top].fa) {
re+=seg::query(rt[p],1,Trie::tim,Trie::t[Trie::t[x].top].dfn,Trie::t[x].dfn);
}
printf("%d\n",re);
}
int main() {
int i,j;
// setIO("input");
scanf("%d",&n);
for(i=1;i<=n;++i) {
int op,lst=0;
char str[3];
scanf("%d",&op);
if(op==2) scanf("%d",&lst),lst=Trie::id[lst];
scanf("%s",str);
if(!Trie::t[lst].ch.count(str[0]-'a')) {
Trie::t[lst].ch[str[0]-'a']=i;
Trie::id[i]=i;
}
else Trie::id[i]=Trie::t[lst].ch[str[0]-'a'];
}
Trie::build_tree();
SAM::construct();
scanf("%d",&m);
for(i=1;i<=m;++i)
scanf("%d%s",&j,S+1), solve(j);
return 0;
}