3881: [Coci2015]Divljak
Time Limit: 20 Sec Memory Limit: 768 MBSubmit: 553 Solved: 176
[Submit][Status][Discuss]
Description
Alice有n个字符串S_1,S_2...S_n,Bob有一个字符串集合T,一开始集合是空的。
接下来会发生q个操作,操作有两种形式:
“1 P”,Bob往自己的集合里添加了一个字符串P。
“2 x”,Alice询问Bob,集合T中有多少个字符串包含串S_x。(我们称串A包含串B,当且仅当B是A的子串)
Bob遇到了困难,需要你的帮助。
Input
第1行,一个数n;
接下来n行,每行一个字符串表示S_i;
下一行,一个数q;
接下来q行,每行一个操作,格式见题目描述。
Output
对于每一个Alice的询问,帮Bob输出答案。
Sample Input
3
a
bc
abc
5
1 abca
2 1
1 bca
2 2
2 3
a
bc
abc
5
1 abca
2 1
1 bca
2 2
2 3
Sample Output
1
2
1
2
1
HINT
【数据范围】
1 <= n,q <= 100000;
Alice和Bob拥有的字符串长度之和各自都不会超过 2000000;
字符串都由小写英文字母组成。
Source
分析:
好神的题目...OTZ...
我们要求的是一个串在多少个串中出现了,这个问题可以用fail树来解决...
我们先拿出所有的$S$集合中的串建AC自动机,然后把fail树拎出来,每一次我们在$P$集合中新添加一个串的时候就把这个串在AC自动机上去匹配,把匹配的节点记录下来,这些节点在fail树上到根节点的路径上的所有点在$P$集合中的出现次数都增加了1,但是要去重,怎么办?树连剖分?复杂度爆炸...于是有一个机智的做法,我们把每一节点到根节点的路径的上的点的权值都+1,然后把点按照dfs序排序,把相邻两个节点的$lca$到根节点的路径上的点的权值全部-1...但是如果真的路径修改单点查询很麻烦,所以我们把权值的加减标记全部累加的节点上,每次询问的时候统计子树的标记和,用树状数组+dfs序维护就好了...
代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> //by NeighThorn using namespace std; const int maxm=2000000+5,maxn=100000+5; int n,m,cnt,dfn,tot,top,head,tail,q[maxm],fa[maxm][25],be[maxm],en[maxm],hd[maxm],to[maxm],nxt[maxm],dep[maxm],stk[maxm],node[maxn]; char s[maxm],t[maxm]; struct trie{ int fail,nxt[26]; }tr[maxm]; struct Tree{ int tree[maxm]; inline void insert(int x,int y){ for(;x<=dfn;x+=x&-x) tree[x]+=y; } inline int query(int x){ int res=0; for(;x;x-=x&-x) res+=tree[x]; return res; } }T; inline void add(int x,int y){ to[cnt]=y;nxt[cnt]=hd[x];hd[x]=cnt++; } inline void insert(char *s,int id){ int p=0,len=strlen(s); for(int i=0;i<len;i++){ if(!tr[p].nxt[s[i]-'a']) tr[p].nxt[s[i]-'a']=++tot,tr[tot].fail=-1; p=tr[p].nxt[s[i]-'a']; } node[id]=p; } inline void buildACM(void){ head=0,tail=0,q[0]=0; while(head<=tail){ int id=q[head++],p=-1; for(int i=0;i<26;i++){ if(tr[id].nxt[i]){ if(id){ p=tr[id].fail; while(p!=-1){ if(tr[p].nxt[i]){ tr[tr[id].nxt[i]].fail=tr[p].nxt[i]; break; } p=tr[p].fail; } if(p==-1) tr[tr[id].nxt[i]].fail=0; } else tr[tr[id].nxt[i]].fail=0; q[++tail]=tr[id].nxt[i]; } else if(id) tr[id].nxt[i]=tr[tr[id].fail].nxt[i]; } } } inline void dfs(int root){ be[root]=++dfn; for(int i=hd[root];i!=-1;i=nxt[i]) dep[to[i]]=dep[root]+1,fa[to[i]][0]=root,dfs(to[i]); en[root]=dfn; } inline void init(void){ for(int j=1;j<=20;j++) for(int i=1;i<=tot;i++) fa[i][j]=fa[fa[i][j-1]][j-1]; } inline int LCA(int x,int y){ if(dep[x]<dep[y]) swap(x,y); int d=dep[x]-dep[y]; for(int i=0;i<=20;i++) if((d>>i)&1) x=fa[x][i]; if(x==y) return x; for(int i=20;i>=0;i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return fa[x][0]; } inline bool cmp(int x,int y){ return be[x]<be[y]; } signed main(void){ memset(hd,-1,sizeof(hd)); scanf("%d",&n);tr[0].fail=-1; for(int i=1;i<=n;i++) scanf("%s",s),insert(s,i); buildACM();scanf("%d",&m); for(int i=1;i<=tot;i++) add(tr[i].fail,i); fa[0][0]=0,dfs(0);init(); for(int C=1,opt,x;C<=m;C++){ scanf("%d",&opt); if(opt==1){top=0; scanf("%s",t);int p=0; for(int i=0;t[i];i++){ p=tr[p].nxt[t[i]-'a']; if(p) stk[++top]=p; else break; } sort(stk+1,stk+top+1,cmp); int tmp=top;top=0; for(int i=1;i<=tmp;i++) if(stk[i]!=stk[i-1]) stk[++top]=stk[i]; for(int i=1;i<=top;i++) T.insert(be[stk[i]],1); for(int i=2;i<=top;i++) T.insert(be[LCA(stk[i],stk[i-1])],-1); } else{ scanf("%d",&x);x=node[x]; printf("%d ",T.query(en[x])-T.query(be[x]-1)); } } return 0; }
By NeighThorn