题意:有N个串,给出的形式是拼接给出,对于第i行: (1,c)表示字符串i是单个字母c; (2,p,c)表示字符串i=在字符串p后面接上一个字母c。
然后给出M个提问,形式是(i,string)。问string在字符串i中出现了多少次。
思路:这类题显然是在AC自动机上乱搞。 对于询问的串建立AC自动机,BFS建立fail树;那么一个询问其实就是第i个串的位置到根的这些节点,有多少个在string节点的子树里。 即给一条链染色,然后询问一个点是子树里多少点被染色了。 为了不重不漏, 我们用回溯就可以处理了。 然后用树状数组维护DFS序下的前缀和。
具体的,对于N个串在AC自动机上跑,每跑一步对应N个字符串之一。 每跑到一个节点,就加加。 跑完子树后就减减,保证了加的部分是一条链。
#include<bits/stdc++.h> #define pii pair<int,int> #define pb push_back #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=400010; vector<pii>G[maxn],Q[maxn]; vector<int>F[maxn]; int ch[maxn][26],fail[maxn],ans[maxn],tot; int in[maxn],out[maxn],times; //dfn char s[maxn]; int sum[maxn]; void add(int x,int val){ while(x<=times) sum[x]+=val,x+=(-x)&x;} int query(int x){ int res=0; while(x){ res+=sum[x]; x-=(-x)&x; } return res;} int add() { int now=0,L=strlen(s); rep(i,0,L-1) { if(!ch[now][s[i]-'a']) ch[now][s[i]-'a']=++tot; now=ch[now][s[i]-'a']; } return now; } void build() { queue<int>q; rep(i,0,25) if(ch[0][i]) q.push(ch[0][i]); while(!q.empty()){ int u=q.front(); q.pop(); rep(i,0,25) { if(ch[u][i]){ fail[ch[u][i]]=ch[fail[u]][i]; q.push(ch[u][i]); } else ch[u][i]=ch[fail[u]][i]; } } rep(i,1,tot) F[fail[i]].push_back(i); } void dfs(int u) { in[u]=++times; for(int i=0;i<F[u].size();i++) dfs(F[u][i]); out[u]=times; } void dfs(int u,int now) //u是位置,now是id { add(in[u],1); for(int i=0;i<Q[now].size();i++){ pii t=Q[now][i]; ans[t.second]=query(out[t.first])-query(in[t.first]-1); } for(int i=0;i<G[now].size();i++){ pii t=G[now][i]; dfs(ch[u][t.first],t.second); } add(in[u],-1); } int main() { int N,M,opt,p; scanf("%d",&N); rep(i,1,N) { scanf("%d",&opt); if(opt==1) p=0; else scanf("%d",&p); scanf("%s",s); G[p].pb(pii(s[0]-'a',i)); } scanf("%d",&M); rep(i,1,M) { scanf("%d%s",&p,s); Q[p].pb(pii(add(),i)); } build(); dfs(0); dfs(0,0); rep(i,1,M) printf("%d ",ans[i]); return 0; }