zoukankan      html  css  js  c++  java
  • 洛谷 P2414 [NOI2011] 阿狸的打字机(AC自动机、树状数组)

    传送门


    解题思路

    被细节卡了一上午wwww

    很好地一道题,考察了AC自动机的本质。

    先考虑如何暴力做:枚举y中的每一位,并不断跳fail,若跳到了x,则ans++。

    如何优化这个过程呢?

    先离线根据y从小到大排序。
    当我们以0为根节点,fail[now]向now连边时,AC自动机就变成了一颗树。这时我们把上述问题放在树上,就变成了询问以x的结尾为根的子树里有多少位y。

    可以用dfs序+树状数组/线段树实现。

    具体建AC自动机过程:

    • B:now-->fa[now]
    • P:打标记
    • 字符:继续走。

    求解过程:

    • B: dfn[now]对应的点权-1,now-->fa[now]
    • P:回答询问,查询区间和。
    • 字符:继续走,并使dfn[now]对应的点权+1

    AC代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #include<map>
    using namespace std;
    const int maxn=1e5+5;
    int n,m,cnt,p[maxn],siz[maxn],dfn[maxn],times,fa[maxn],num[maxn],tr[maxn][30],fail[maxn],tot,d[maxn];
    string s;
    struct node{
    	int v,next;
    }e[maxn];
    struct Node{
    	int x,y,id,ans;
    }q[maxn];
    bool cmp1(Node a,Node b){
    	return a.y<b.y;
    }
    bool cmp2(Node a,Node b){
    	return a.id<b.id;
    }
    void insert(int u,int v){
    	cnt++;
    	e[cnt].v=v;
    	e[cnt].next=p[u];
    	p[u]=cnt;
    }
    void dfs(int u){
    	siz[u]=1;
    	dfn[u]=++times;
    	for(int i=p[u];i!=-1;i=e[i].next){
    		int v=e[i].v;
    		dfs(v);
    		siz[u]+=siz[v];
    	}
    }
    void inserts(){
    	int len=s.length(),now=0;
    	for(int i=0;i<len;i++){
    		if(s[i]=='B') now=fa[now];
    		else if(s[i]=='P') num[++m]=now;
    		else{
    			if(tr[now][s[i]-'a']) now=tr[now][s[i]-'a'];
    			else{
    				tr[now][s[i]-'a']=++tot;
    				fa[tot]=now;
    				now=tot;
    			}
    		}
    	}
    }
    void build(){
    	queue<int> q;
    	for(int i=0;i<26;i++){
    		if(tr[0][i]) q.push(tr[0][i]),insert(0,tr[0][i]);
    	}
    	while(!q.empty()){
    		int now=q.front();q.pop();
    		for(int i=0;i<26;i++){
    			if(tr[now][i]) fail[tr[now][i]]=tr[fail[now]][i],q.push(tr[now][i]),insert(fail[tr[now][i]],tr[now][i]);
    			else tr[now][i]=tr[fail[now]][i];
    		}
    	}
    }
    inline int lowbit(int x){
    	return x&(-x); 
    }
    void update(int x,int v){
    	for(int i=x;i<maxn;i+=lowbit(i)) d[i]+=v;
    }
    int query(int x){
    	int res=0;
    	for(int i=x;i>=1;i-=lowbit(i)) res+=d[i];
    	return res; 
    }
    void work(){
    	int len=s.length(),now=0,cntq=1;
    	for(int i=0;i<len;i++){
    		if(s[i]=='B'){
    			update(dfn[now],-1);
    			now=fa[now];
    		}
    		else if(s[i]=='P'){
    			while(cntq<=n&&num[q[cntq].y]==now){
    				int x1=query(dfn[num[q[cntq].x]]+siz[num[q[cntq].x]]-1),x2=query(dfn[num[q[cntq].x]]-1);
    				q[cntq].ans=x1-x2;
    				cntq++;
    			}
    		}
    		else{
    			now=tr[now][s[i]-'a'];
    			update(dfn[now],1);
    		}
    	}
    }
    int main(){
    	ios::sync_with_stdio(false);
    	memset(p,-1,sizeof(p));
    	cin>>s;
    	inserts();
    	build();
    	dfs(0); 
    	cin>>n;
    	for(int i=1;i<=n;i++){
    		q[i].id=i;
    		cin>>q[i].x>>q[i].y;
    	}
    	sort(q+1,q+n+1,cmp1);
    	work();
    	sort(q+1,q+n+1,cmp2);
    	for(int i=1;i<=n;i++) cout<<q[i].ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    js中的数组
    range关键字,map,sync.Map,list
    数组,切片
    类型转换,指针,变量的生命周期,常量,模拟枚举,类型别名和类型定义
    字符串类型及其常用操作
    整数类型,浮点类型,复数,bool类型
    声明,初始化,匿名变量,作用域
    C#并发编程——异步编程基础
    继承、多态、接口
    C#基础
  • 原文地址:https://www.cnblogs.com/yinyuqin/p/15397919.html
Copyright © 2011-2022 走看看