• 【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;
    }
    
  • 相关阅读:
    angularjs中的jqlite的认识理解及操作使用
    轻谈Normalize.css
    oppo R9 WLAN使用代理图解
    前端小炒的win7使用笔记(收藏篇)
    git操作方便,简单使用的客户端sourcetree 安装配置所遇问题总结
    关于js中对事件绑定与普通事件的理解
    IT最新最火的网络词汇*2*(文章来源电商)
    Git笔记之初识vi编辑器
    番茄钟工作法--我们天生爱分享
    探讨"点"语法的奥秘
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2434.html
Copyright © 2020-2023  润新知