BZOJ 3881: [Coci2015]Divljak

3881: [Coci2015]Divljak

Time Limit: 20 Sec  Memory Limit: 768 MB
Submit: 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

Sample Output

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

posted @ 2017-02-17 00:14  NeighThorn  阅读(184)  评论(0编辑  收藏  举报