zoukankan      html  css  js  c++  java
  • 【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
    ",ans[i]);return 0;
    }
    
  • 相关阅读:
    mysql修改数据表名
    HDU 5742 It's All In The Mind (贪心)
    HDU 5752 Sqrt Bo (数论)
    HDU 5753 Permutation Bo (推导 or 打表找规律)
    HDU 5762 Teacher Bo (暴力)
    HDU 5754 Life Winner Bo (博弈)
    CodeForces 455C Civilization (并查集+树的直径)
    CodeForces 455B A Lot of Games (博弈论)
    CodeForces 455A Boredom (DP)
    HDU 4861 Couple doubi (数论 or 打表找规律)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2434.html
Copyright © 2011-2022 走看看