洛谷2414 BZOJ2434 NOI2011 阿狸的打字机 AC自动机 树状数组

题目链接

题意:
给你一个读入串,其中包含多个字符串。字符集是小写字母,如果读到B,表示当前串删除上一个字母,如果读到P,表示当前串形成了一个新串,但是原来的的字符串并不消失,会继续成为下一个串的前缀。我们给每个串按产生的顺序标上序号,每次会给出两个询问串的标号,问第x个串在第y个串中出现了多少次。总串长<=1e5,询问次数<=1e5。

题解:
是一个我觉得写起来有点麻烦的题。

首先问题是个出现次数题,涉及到字符串匹配,最常用的就是KMP和AC自动机,于是优先从这两个算法中考虑。由于是多个串和1e5级别的询问,所以没法用KMP这种单串匹配的方法去每次两两计算答案,于是考虑能不能用AC自动机来做。

我们考虑建出AC自动机。我们发现所有串的总串长可能很长,没法一个一个加,但是我们发现,加上一个字符、删去一个字符,都可以看作在一棵trie树上走,于是不用每次把串拿出来重新插入,只要在之前的基础上继续在trie树上走就好了。这样就可以建出trie树了。这个题题目没有说,但是据说数据保证了每个串都是本质不同的,于是我就懒得写可能有本质相同的情况了。

我们考虑其实如果A串在B串中出现过,那么我们建出AC自动机的fail树之后,应该trie树上表示B串的某个字符的点一定会经过若干次fail指针的跳跃,跳到A串在trie树上的结束节点。而如果A在B中多次出现,那么一定会有多个B串中的点能经过若干次沿着fail指针的跳跃跳到A的结束节点。我们知道,fail指针在形态上会形成一个树形结构,于是我们可以发现,其实A在B中出现的次数,就是trie树上从根到B串的结束节点的路径上,有多少个点在A的结束节点在fail树上的子树内。这些可能还是可以想到的,但是怎么数一个串在fail树上某一个点的子树内有多少个点呢?

我们知道,子树有一个性质是dfs序是连续的一段,我们把fail树建出来,然后求出trie树上每一个点在fail树上的dfs序,我们考虑维护这个dfs序。我们的做法是,把询问离线下来,然后在trie树上一边dfs一边做。dfs到了某一个节点,如果这个节点是一个结束节点,那么就回答所有某个串出现在当前这个节点表示的串中的次数。我们用一个树状数组维护dfs序,到了一个点就把他的dfs序加入树状数组,dfs完了整个子树就把这个点从树状数组中减去,就能在到了trie树上的一个串的结束节点时,维护的信息正好是这个串的信息。那么回答时我们只需要查询问的串在fail树上所在的子树的那一段dfs序的区间和就可以了。

这样这个题就做完了,总复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。说清楚都挺麻烦的,写起来也不是很好写的。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,ans[200010],u[200010],v[200010],cnt,cur,num,cnt1;
int fa[200010],ji[200010],hed[400010],hed1[400010],xu[400010];
int vis[400010],val[400010],ed[400010];
char s[200010];
struct node
{
	int fail,vis[26],ed,c;
}tr[400010];
struct edge
{
	int to,next,id;
}a[400010],b[400010];
queue<int> q;
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
inline void add(int from,int to,int id)
{
	a[++cnt].to=to;
	a[cnt].id=id;
	a[cnt].next=hed[from];
	hed[from]=cnt;
}
inline void build()
{
	for(int i=0;i<=25;++i)
	{
		if(tr[1].vis[i])
		{
			q.push(tr[1].vis[i]);
			tr[tr[1].vis[i]].fail=1;
		}
	}
	tr[1].fail=1;
	while(!q.empty())
	{
		int x=q.front();
		q.pop(); 
		if(tr[x].fail!=1)
		{
			int gg=tr[fa[x]].fail;
			while(1)
			{
				if(tr[gg].vis[tr[x].c]!=0)
				{					
					gg=tr[gg].vis[tr[x].c];
					break;
				}
				if(gg==1)
				break;
				gg=tr[gg].fail;
			}
			tr[x].fail=gg;
		}
		for(int i=0;i<=25;++i)
		{
			if(tr[x].vis[i])
			q.push(tr[x].vis[i]);						
		}
	}
}
inline void add1(int from,int to)
{
	b[++cnt1].to=to;
	b[cnt1].next=hed1[from];
	hed1[from]=cnt1;
}
inline void dfs(int x)
{
	vis[x]=1;
	xu[x]=++num;
	for(int i=hed1[x];i;i=b[i].next)
	{
		int y=b[i].to;
		if(vis[y])
		continue;
		dfs(y);
	}
	ed[x]=num;
}
inline void update(int x,int opt)
{
	if(opt==1)
	{
		for(int i=x;i<=cnt;i+=(i&(-i)))
		val[i]++;
	}
	else
	{
		for(int i=x;i<=cnt;i+=(i&(-i)))
		val[i]--;
	}
}
inline int query(int x)
{
	int res=0;
	for(int i=x;i>=1;i-=(i&(-i)))
	res+=val[i];
	return res;
}
inline void dfs2(int x)
{
	update(xu[x],1);
	if(tr[x].ed)//ÀÁµÃд¿ÉÄÜÓб¾ÖÊÏàͬµÄ´®µÄÇé¿öÁË 
	{
		for(int i=hed[tr[x].ed];i;i=a[i].next)
		{
			int y=a[i].to,z;
			z=ji[y];
			ans[a[i].id]=query(ed[z])-query(xu[z]-1);
		}
	}
	for(int i=0;i<=25;++i)
	{
		if(tr[x].vis[i])
		dfs2(tr[x].vis[i]);
	}
	update(xu[x],-1);
}
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	m=read();
	for(int i=1;i<=m;++i)
	{
		u[i]=read();
		v[i]=read();
		add(v[i],u[i],i);
	}
	cur=1;
	cnt=1;
	for(int i=1;i<=n;++i)
	{
		if(s[i]=='P')
		{
			ji[++num]=cur;
			tr[cur].ed=num;
		} 		
		else if(s[i]=='B')
		cur=fa[cur];
		else
		{
			int x=s[i]-'a';
			if(!tr[cur].vis[x])
			{
				tr[cur].vis[x]=++cnt;
				tr[cnt].c=x;
				fa[tr[cur].vis[x]]=cur;
			}
			cur=tr[cur].vis[x];
		}
	}	
	build();
	for(int i=2;i<=cnt;++i)
	{
		add1(i,tr[i].fail);
		add1(tr[i].fail,i);
	}
	num=0;
	dfs(1);
	dfs2(1);
	for(int i=1;i<=m;++i)
	printf("%d\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值