传送门
询问y中x出现了多少次,相当于询问y中的节点通过fail指针能到达x的ed节点的有多少个。
那么一种比较暴力的做法就是,建出AC自动机,把询问按y排个序,遍历trie树,每次暴力跳fail,开个cnt数组维护对每个x的答案。
考虑y上的点跳fail指针最终到达某个ed点的过程,把fail指针当作边并且反向,发现y上能跳到x的ed的节点都在fail树上x的子树中。如果把fail树建出来,现在只需要考虑怎么统计x的ed点的子树中有多少个属于y的点。
结合刚刚暴力的做法,发现遍历trie树走到y的ed节点时,栈内保存的经过点就是y串。也就是说,在走到y的ed点时,可以做到将y上的所有点都标记出来,并且只标记y上的点。这个时候对于一个询问(x,y),只需要求fail树上x的ed点的子树中有多少个被标记出来的点。dfs一遍fail树,将每个点映射到dfs序。子树的dfs序是连续的,于是问题就变成了询问一段连续区间内的值,树状数组维护即可。
对于trie树上的每个点开个vector记录它是哪个串的ed点,遍历到一个点的时候处理以它结尾的串的询问。这里的做法没有排序询问,而把每个询问存入y的vector中,处理每个串的时候遍历这个vector。
时间复杂度是O(nlogn)。
#include#include #include #include #include using namespace std; const int N=1e5+10; char s[N]; int fa[N],lens,now,tree[N][26],tree1[N][26],num,ed[N],tot,m,fail[N],ans[N]; struct node{ int x,id; node(int a=0,int b=0){ x=a,id=b; } }; vector v[N]; vector<int>e[N]; void insert(){ for(int j=1;j<=lens;j++){ if(s[j]=='B')now=fa[now]; else if(s[j]=='P'){ num++; ed[num]=now; e[now].push_back(num); } else{ if(!tree[now][s[j]-'a'])tree1[now][s[j]-'a']=tree[now][s[j]-'a']=++tot,fa[tot]=now; now=tree[now][s[j]-'a']; } } } int Next[N],head[N],tot1,ver[N]; void add(int x,int y){ ver[++tot1]=y; Next[tot1]=head[x]; head[x]=tot1; } queue<int>q0; void getfail(){ for(int i=0;i<26;i++){ if(tree[0][i]){ add(0,tree[0][i]); q0.push(tree[0][i]); } } while(q0.size()){ int u=q0.front(); q0.pop(); for(int i=0;i<26;i++){ int v=tree1[u][i]; if(v){ fail[v]=tree1[fail[u]][i]; add(fail[v],v); q0.push(v); } else tree1[u][i]=tree1[fail[u]][i]; } } } int tim,rec[N],rec1[N]; void dfs(int x){ rec[x]=++tim; for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs(y); } rec1[x]=tim; } int tr[N]; void add1(int x,int val){ for(;x<=tim;x+=(x&-x))tr[x]+=val; } int ask(int x){ int sum=0; for(;x;x-=(x&-x))sum+=tr[x]; return sum; } void solve(int x){ for(int i=0;i ){ node y=v[x][i]; ans[y.id]=ask(rec1[ed[y.x]])-ask(rec[ed[y.x]]-1); } } void dfs1(int x){ if(e[x].size()){ for(int i=0;i ){ int y=e[x][i]; solve(y); } } for(int i=0;i<26;i++){ if(tree[x][i]){ add1(rec[tree[x][i]],1); dfs1(tree[x][i]); add1(rec[tree[x][i]],-1); } } } int main() { scanf("%s",s+1); lens=strlen(s+1); insert(); getfail(); dfs(0); scanf("%d",&m); for(int i=1,x,y;i<=m;i++){ scanf("%d%d",&x,&y); v[y].push_back(node(x,i)); } dfs1(0); for(int i=1;i<=m;i++)printf("%d\n",ans[i]); return 0; }
·建立trie树的时候直接根据操作在树上走动就好了。操作是小写字母的话就往儿子走一步,操作B则往父亲走一步,操作P的话记录当前点为当前串的ed节点并把当前串丢进节点的vector里去。用不着每次暴力插入一个新串,正解被我硬生生搞成70分暴力…
·注意映射关系的细节。
·由于最后还要遍历一遍trie树,前面getfail的时候不能把原trie树建成trie图,可以备份一棵trie树用来getfail。