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\n",T.query(en[x])-T.query(be[x]-1));
}
}
return 0;
}
By NeighThorn