BZOJ 3881: [Coci2015]Divljak 【AC自动机fail树】

本文介绍了一种使用AC自动机处理字符串匹配问题的有效方法。通过构建AC自动机并结合树状数组进行区间修改与单点查询,实现了高效地解决字符串集合中包含特定子串的问题。该方法适用于动态更新字符串集合并快速响应查询。
题面:

Alice有n个字符串S1,S2...SnS_1,S_2...S_nS1,S2...Sn,Bob有一个字符串集合T,一开始集合是空的。
接下来会发生q个操作,操作有两种形式:
“1 P”,Bob往自己的集合里添加了一个字符串P。
“2 x”,Alice询问Bob,集合T中有多少个字符串包含串SxS_xSx。(我们称串A包含串B,当且仅当B是A的子串)
1 <= n,q <= 100000;
Alice和Bob拥有的字符串长度之和各自都不会超过 2000000;

题目分析:

S串是不变的,对S建立AC自动机。
把串T放到自动机中,经过的节点以及fail指针对应的那些节点的被包含次数都会+1。
但是不能重复统计。按照树剖+树状数组的做法,就是把树链合并,然后区间修改,单点查询。这样的复杂度是O(lenlog2len)O(lenlog^2len)O(lenlog2len)的,想必不能承受。

把fail指针构成的fail树建出来,一个串的被包含次数就是子树中的点被经过的次数,但是对于一个T串只能记一次。
那么把经过的点按照dfs序排序,在每个点的位置+1,相邻两点的lca处-1,查询的时候就查询子树的和(单点修改区间查询),这样可以保证对于一个被包含的串i,串T对它子树的贡献和为1。复杂度是O(lenloglen)O(lenloglen)O(lenloglen)

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 2000005
#define maxc 26
using namespace std;
int n,m,pos[maxn],in[maxn],out[maxn],tim,seq[maxn],siz[maxn],son[maxn],top[maxn],fa[maxn],dep[maxn];
int fir[maxn],nxt[maxn],to[maxn],tot;
inline void line(int x,int y){nxt[++tot]=fir[x];fir[x]=tot;to[tot]=y;}
inline bool cmp(int i,int j){return in[i]<in[j];}
struct ac_automaton{
	int ch[maxn][maxc],fail[maxn],sz;
	void insert(char *s,int id){
		int len=strlen(s),r=0,v;
		for(int i=0;i<len;i++,r=ch[r][v])
			if(!ch[r][v=s[i]-'a']) ch[r][v]=++sz;
		pos[id]=r;
	}
	int Q[maxn],head,tail;
	void build(){
		Q[head=tail=0]=0;
		while(head<=tail){
			int r=Q[head++],c;
			for(int i=0;i<maxc;i++)
				if(c=ch[r][i]) fail[c]=r?ch[fail[r]][i]:0,Q[++tail]=c,line(fail[c],c);
				else ch[r][i]=ch[fail[r]][i];
		}
	}
}A;
void dfs1(int u){
	in[u]=++tim;siz[u]=1,son[u]=A.sz+1;
	for(int i=fir[u],v;i;i=nxt[i]){
		dep[v=to[i]]=dep[u]+1,fa[v]=u;
		dfs1(v);siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
	out[u]=tim;
}
void dfs2(int u,int tp){
	top[u]=tp;
	if(son[u]!=A.sz+1) dfs2(son[u],tp);
	for(int i=fir[u];i;i=nxt[i]) if(to[i]!=son[u]) dfs2(to[i],to[i]);
}
int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}
int arr[maxn];
void upd(int i,int d){for(;i<=tim;i+=i&-i) arr[i]+=d;}
int qsum(int i){int s=0;for(;i;i-=i&-i) s+=arr[i];return s;}
char s[maxn];
int main()
{
	int op,x;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%s",s),A.insert(s,i);
	A.build(); siz[A.sz+1]=0,dfs1(0),dfs2(0,0);
	scanf("%d",&m);
	while(m--){
		scanf("%d",&op);
		if(op==1){
			scanf("%s",s);int len=strlen(s);
			for(int i=0,r=0;i<len;i++) seq[i]=(r=A.ch[r][s[i]-'a']);
			sort(seq,seq+len,cmp);
			upd(in[seq[0]],1);
			for(int i=1;i<len;i++) if(seq[i]!=seq[i-1]) 
				upd(in[seq[i]],1),upd(in[LCA(seq[i-1],seq[i])],-1);
		}
		else scanf("%d",&x),printf("%d\n",qsum(out[pos[x]])-qsum(in[pos[x]]-1));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值