BZOJ 2434 [Noi2011]阿狸的打字机 - AC自动机+树状数组

这道题要用到fail树,学习到了一点知识。

大概就是在一个字串尾节点上找另一段字串的全部节点指向此节点的fail有多少个。然后常规思路很明显没有办法,于是可以改成一颗fail树,从根节点出发通过fial路径连向每一个结点。查询的是子串,那么势必是长串fail指向子串尾节点,由此尾节点一定是长串中某些结点的fail树上的父亲结点。

一般这种在很短时间内统计个数深度的题目都会考虑到数据结构。联系到是树形,于是先搞出fail树的dfs序,然后in和out之间的编号是其所有子节点,维护一个树状数组,查询in和out这一段的sum即可。注意答案需要离线。由于此题的输入为连续模拟,于是acm建好后再模拟一遍,进入时树状数组+1,出队时-1,于是到某一'P'(尾节点)有标记的始终是当前的串上的节点,利用小串的尾节点in,out查询即可。


#include<iostream>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<cstdio>
#include<algorithm>

using namespace std;

const int maxn=100005;

struct edge
{
	int to,next;
}e[maxn],q[maxn];

int n,m;
int cnt_clock,icnt,qcnt;
char s[maxn];
int head[maxn],qhead[maxn];
int c[maxn<<1];
int in[maxn],out[maxn];
int ans[maxn];

void insert(int a,int b)
{
	e[++icnt].to=b;e[icnt].next=head[a];head[a]=icnt;
}
void qinsert(int a,int b)
{
	q[++qcnt].to=b;q[qcnt].next=qhead[a];qhead[a]=qcnt;
}
int query(int x)
{
	int res=0;
	for(int i=x;i>0;i-=i&(-i))res+=c[i];
	return res;
}
void add(int x,int val)
{
	for(int i=x;i<=cnt_clock;i+=i&(-i))c[i]+=val;
}
struct 
{
	int cnt,id;
	int fail[maxn],next[maxn][27];
	int pre[maxn],end[maxn];
	void init()
	{
		scanf("%d",&m);
		for(int i=1;i<=m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			qinsert(b,a);
		}
		
		cnt=1;
		for(int i=0;i<26;i++)
			next[0][i]=1;
	}
	void trie()
	{
		int now=1;
		for(int i=1;i<=n;i++)
		{
			if(s[i]=='P')
				end[++id]=now;
			else if(s[i]=='B')now=pre[now];
			else
			{
				if(!next[now][s[i]-'a'])next[now][s[i]-'a']=++cnt;
				pre[next[now][s[i]-'a']]=now;
				now=next[now][s[i]-'a'];
			}
		}
	}
	void getfail()
	{
		queue<int>q;
		q.push(1);//这里老是写错
		fail[1]=0;//
		insert(0,1);
		while(!q.empty())
		{
			int u=q.front();
			q.pop();
			for(int i=0;i<26;i++)
				if(next[u][i])
			{
				int now=fail[u];
				while(now&&!next[now][i])now=fail[now];
				fail[next[u][i]]=next[now][i];
				insert(next[now][i],next[u][i]);
				q.push(next[u][i]);	
			}
		}
	}
	void solve()
	{
		int now=1;
		id=0;
		add(in[1],1);
		for(int i=1;i<=n;i++)
		{
			if(s[i]=='P')
			{
				id++;
				for(int i=qhead[id];i;i=q[i].next)
				{
					int x=end[q[i].to];
					ans[i]=query(out[x])-query(in[x]-1);
				}
			}
			else if(s[i]=='B')add(out[now],-1),now=pre[now];
			else add(in[next[now][s[i]-'a']],1),now=next[now][s[i]-'a'];
		}
	}
	void print()
	{
		for(int i=1;i<=m;i++)
			printf("%d\n",ans[i]);
	}
}acm;
void dfs(int x)
{
	in[x]=++cnt_clock;
	for(int i=head[x];i;i=e[i].next)
		dfs(e[i].to);
	out[x]=++cnt_clock;
}
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	acm.init();
	acm.trie();
	acm.getfail();
	dfs(0);
	acm.solve();
	acm.print();
	return 0;
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值