【BZOJ2434】【NOI2011】阿狸的打字机 AC自动机

转载请注明出处233:http://blog.csdn.net/vmurder/article/details/42875307

这是一道神题。


首先我们需要建立AC自动机,然后再建个Fail树,之后发现

    如果询问a串在b串中出现了几次,那么只需要看b串在AC自动机上所有的节点中有多少个节点,在a串的结束节点在Fail树上的子树中就可以了。

然后这样做就很可以了,但是仍然不能AC,

这时我们只需要按照Fail树的dfs序建立数据结构(我写了树状数组)进行区间查询就好了。


这时对于以上的b串,我们按照dfs序扫一遍(按照输入时的字符串就是天然的优越dfs序),然后每转移一个节点,就在数据结构上加加减减、、

然后对于每个‘B’,挂链询问中问了哪些‘A’在其中出现了多少次,树状数组O(logn)查一次就好了。


单点修改区间查询,时间复杂度mlogn。

代码:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 101000
#define MAX 220000
#define T 26
using namespace std;
struct FAIL
{
	int v[N],next[N],head[N],cnt;
	void cls()
	{
		cnt=0;
		memset(head,0,sizeof head);
	}
	void add(int u,int _v)
	{
		v[++cnt]=_v;
		next[cnt]=head[u];
		head[u]=cnt;
	}
	int in[N],out[N],dfn;
	void build_dfn(int x=0)
	{
		in[x]=++dfn;
		for(int i=head[x];i;i=next[i])
			build_dfn(v[i]);
		out[x]=++dfn;
	}
}Fail;

struct FENWICK //树状数组
{
	int fenwick[MAX];
	inline void add (int x,int w)
	{
		for(x=Fail.in[x];x<MAX;x+=(x&-x))
			fenwick[x]+=w;
	}
	inline int query(int x)
	{
		int ans=0,temp=x;
		for(x=Fail.out[temp] ;x;x-=(x&-x))ans+=fenwick[x];
		for(x=Fail.in[temp]-1;x;x-=(x&-x))ans-=fenwick[x];
		return ans;
	}
}fw;

struct TRIE
{
	int pa[N],next[N][T],fail[N];
	int end[N],crs[N];
	int root,cnt,id;
	char s[N];
	void build_trie() // 建立Trie
	{
		scanf("%s",s);
		int i,x=root=0,alp;
		for(i=0;s[i];i++)
		{
			if(s[i]=='P')end[x]=++id,crs[id]=x;
			else if(s[i]=='B')x=pa[x];
			else {
				alp=s[i]-'a';
				if(!next[x][alp])next[x][alp]=++cnt,pa[cnt]=x;
				x=next[x][alp];
			}
		}
	}
	void build_fail() // 建立AC自动机
	{
		queue<int>q;
		q.push(root);
		int i,u,v,temp;
		while(!q.empty())
		{
			u=q.front(),q.pop();
			for(i=0;i<T;i++)if(v=next[u][i])
			{
				if(u==root)fail[v]=root;
				else {
					temp=fail[u];
					while(temp&&!next[temp][i])temp=fail[temp];
					fail[v]=next[temp][i];
				}
				q.push(v);
				Fail.add(fail[v],v);
			}
		}
	}
	int v[N],nxt[N],head[N];
	void add(int u,int _v) // 询问离线
	{
		v[++cnt]=_v;
		nxt[cnt]=head[u];
		head[u]=cnt;
	}
	int m,fians[N];
	void input() // 输入询问
	{
		int a,b;
		scanf("%d",&m);
		cnt=0;
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&a,&b);
			add(b,a);
		}
	}
	void query(int pdd)
	{
		for(int i=head[pdd];i;i=nxt[i])
			fians[i]=fw.query(crs[v[i]]);
	}
	void work() // 遍历B串
	{
		int i,x=root=0,alp,now;
		for(i=0;s[i];i++)
		{
			if(s[i]=='P')query(end[x]);
			else if(s[i]=='B')fw.add(x,-1),x=pa[x];
			else {
				alp=s[i]-'a';
				x=next[x][alp];
				fw.add(x,1);
			}
		}
		return ;
	}
	void output() // 输出答案
	{for(int i=1;i<=m;i++)printf("%d\n",fians[i]);}
}Trie;
int main()
{
//	freopen("my.in","r",stdin);
//	freopen("my.out","w",stdout);
	Trie.build_trie();
	Trie.build_fail();
	Fail.build_dfn();
	Trie.input();
	Trie.work();
	Trie.output();
	return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值