【BZOJ2434】[NOI2011] 阿狸的打字机(AC自动机+树状数组)

点此看题面

大致题意: 给你一个由小写字母和\(B,P\)组成的操作串,其中\(B\)表示回删,\(P\)表示打印。\(m\)组询问,问你第\(x\)个被打印出来的串在第\(y\)个被打印出来的串中出现了几次。

\(AC\)自动机

这是一道\(AC\)自动机的题目,个人感觉不是特别难的样子,我居然自己做出来了......

显然,我们可以对题目中给出的操作串建出一个包含所有串的\(AC\)自动机(\(B\)操作就相当于是回到父节点)。

然后考虑如何处理询问。

我们需要知道,在\(AC\)自动机上有这样两个性质:

  • \(A\)串是\(B\)串的前缀,说明\(A\)串在\(AC\)自动机上对应的节点在根节点到\(B\)串的路径上。
  • \(A\)串是\(B\)串的后缀,说明\(B\)串在\(AC\)自动机上对应的节点跳了若干次\(fail\)指针后能够到达\(A\)串。

现在要求\(B\)串中\(A\)串的出现次数,就相当于求\(B\)串中有多少个前缀满足\(A\)串是它的后缀。

考虑跳了若干次\(fail\)指针后能够到达\(A\)串,说明在\(AC\)自动机的\(fail\)树上\(A\)串所对应的节点是其父节点。

所以,如果我们给根节点到\(B\)串所对应节点的路径上的所有节点打上\(1\)的标记,那么就相当于询问\(A\)串所对应节点在\(fail\)树上的子树和。

而如何做到快速打标记呢?

考虑这道题给出的字符串之间是有联系的。

即,我们可以离线做。只要再次模拟操作串,每到达一个新的节点就将这个点权值加\(1\),回退一次就将这个点权值减\(1\)。每到达一个打印串,就处理以它作为\(y\)的询问。

至于如何求子树和,只要预处理出\(fail\)树的\(dfs\)序,然后树状数组维护即可。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,len,Nt,Qt,ans[N+5];char s[N+5];
struct Qry
{
	int x,y,id;I Qry(CI a=0,CI b=0,CI p=0):x(a),y(b),id(p){}
	I bool operator < (Con Qry& o) Con {return y<o.y;}//离线,按y排序
}Q[N+5];
namespace Tree//fail树
{
	class TreeArray//树状数组,用于求子树和
	{
		private:
			int a[N+5];
			I int Qry(RI x) {RI t=0;W(x) t+=a[x],x-=x&-x;return t;}
		public:
			I void Add(RI x,CI y) {W(x<=Nt) a[x]+=y,x+=x&-x;}
			I int Qry(CI x,CI y) {return Qry(y)-Qry(x-1);}
	}A;
	int ee,lnk[N+5],d,dI[N+5],dO[N+5];struct edge {int to,nxt;}e[N<<1];
	I void add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}//建边
	I void Init(CI x,CI lst=0)//预处理dfs序
	{
		dI[x]=++d;for(RI i=lnk[x];i;i=e[i].nxt)
			e[i].to^lst&&(Init(e[i].to,x),0);dO[x]=d;
	}
	I void Add(CI x,CI v) {A.Add(dI[x],v);}//单点修改
	I int Qry(CI x) {return A.Qry(dI[x],dO[x]);}//询问子树和
}
namespace AcAutomation//AC自动机
{
	int pos[N+5],q[N+5];struct node {int Nxt,F,S[30];}O[N+5];
	I void Init()//初始化,模拟操作串
	{
		O[1].F=Nt=1;for(RI i=1,x=1,y;i<=len;++i)
		{
			if(s[i]=='B') {x=O[x].F;continue;}if(s[i]=='P') {pos[++n]=x;continue;}
			!O[x].S[y=s[i]&31]&&(O[O[x].S[y]=++Nt].F=x),x=O[x].S[y];
		}
	}
	I void Build()//求出fail指针,并建树
	{
		RI i,k,H=1,T=0;for(i=1;i<=26;++i)
			(O[1].S[i]?O[q[++T]=O[1].S[i]].Nxt:O[1].S[i])=1;
		W(H<=T) for(k=q[H++],i=1;i<=26;++i)
			(O[k].S[i]?O[q[++T]=O[k].S[i]].Nxt:O[k].S[i])=O[O[k].Nxt].S[i];
		for(i=2;i<=Nt;++i) Tree::add(O[i].Nxt,i);Tree::Init(1);//建树
	}
	I void Solve()//离线处理询问
	{
		for(RI i=1,x=1,m=0,k=1;i<=len;++i)
		{
			if(s[i]=='B') {Tree::Add(x,-1),x=O[x].F;continue;}//回退将权值减1
			if(s[i]^'P') {x=O[x].S[s[i]&31],Tree::Add(x,1);continue;}//到达新的节点将权值加1
			++m;W(Q[k].y==m) ans[Q[k].id]=Tree::Qry(pos[Q[k].x]),++k;//处理以当前打印串为y 的询问
		}
	}
}
int main()
{
	using namespace AcAutomation;
	RI i;scanf("%s",s+1),len=strlen(s+1),Init(),Build();
	for(scanf("%d",&Qt),i=1;i<=Qt;++i) scanf("%d%d",&Q[i].x,&Q[i].y),Q[i].id=i;
	for(sort(Q+1,Q+Qt+1),Solve(),i=1;i<=Qt;++i) printf("%d\n",ans[i]);return 0;
}
posted @ 2020-02-03 11:04  TheLostWeak  阅读(...)  评论(...编辑  收藏