zoukankan      html  css  js  c++  java
  • CF G. Indie Album 广义后缀自动机+树链剖分+线段树合并

    这里给出一个后缀自动机的做法.
    假设每次询问 $t$ 在所有 $s$ 中的出现次数,那么这是非常简单的:
    直接对 $s$ 构建后缀自动机,随便维护一下 $endpos$ 大小就可以.
    然而,想求 $t$ 在 $trie$ 树中一个节点到根的字符串中的出现次数就难了很多.
    我们慢慢讲:
    首先,我们对题中给的 $trie$ 树(即所有 $s$ 串)构建广义后缀自动机.
    因为后缀自动机能识别所有的子串,所以可以直接将 $t$ 在自动机上匹配.
    假设匹配成功,即得到终止节点 $p$.
    那么我们想求 $s_{i}$ 中包含 $p$ 的个数.
    令 $s_{i}$ 对应到 $trie$ 树的终止节点为 $end(i)$ ,那么将 $end(i)$ 到根节点的所有字符都插入后缀自动机之后,对答案有贡献的就是 $trie$ 中 $end(i)$ 到根中后缀包含 $t$ 的节点个数.
    而巧妙的是,对 $s_{i}$ 有贡献的所有 $trie$ 中的节点之间的位置关系是链的关系.
    即他们都在 $end(i)$ 到根节点这条路径上.
    于是,我们就联想,什么数据结构,能让深度递增的节点编号连续呢?
    ——树链剖分.
    没错,我们对 $trie$ 来一遍树剖求解每个点的树剖序.
    对后缀自动机每一个节点建一个线段树,维护的是后缀树中该节点为根子树中所有树剖序的所有标号种类.
    对于这个,我们可以在扩展 $trie$ 树字符的时候就在自动机中新建节点插入该点的树剖序,然后扩展完 $trie$ 树后再来一遍线段树合并.
    最后,只需暴力跳重链,并查询 $dfn[top[x]]$~$dfn[x]$ 在 $p$ 节点所在线段树中有几个出现.
    时间复杂度为 $O(nlog^2n)$,常数巨大,远没有 AC 自动机好写,运行快.
    不过,这确实是一道练习后缀自动机的好题.

    #include <map>
    #include <vector> 
    #include <queue> 
    #include <cstdio>  
    #include <cstring> 
    #include <algorithm>
    #define N 400003 
    #define setIO(s) freopen(s".in","r",stdin) 
    using namespace std;  
    char S[N]; 
    int n,m,rt[N<<1];   
    namespace Trie {
    	int id[N],tim; 
    	struct Node {    
    		map<int,int>ch;   
    		int siz,dfn,top,son,fa;     
    	}t[N];  
    	void dfs1(int u) { 
    		t[u].siz=1; 
    		for(int i=0;i<27;++i) 
    			if(t[u].ch.count(i)) {    
    				int v=t[u].ch[i]; 
    				t[v].fa=u,dfs1(v),t[u].siz+=t[v].siz;   
    				if(!t[u].son||t[v].siz>t[t[u].son].siz) t[u].son=v;    
    			}
    	} 
    	void dfs2(int u,int tp) {
    		t[u].top=tp; 
    		t[u].dfn=++tim;   
    		if(t[u].son) dfs2(t[u].son,tp); 
    		for(int i=0;i<27;++i) 
    			if(t[u].ch.count(i)) { 
    				int v=t[u].ch[i]; 
    				if(v!=t[u].son) 
    					dfs2(v,v); 
    			} 
    	}
    	void build_tree() {  
    		dfs1(0), dfs2(0,0);    
    	}
    }; 
    namespace seg { 
    	#define ls t[x].lson 
    	#define rs t[x].rson
    	int tot; 
    	struct Node { 
    		int lson,rson,sum; 
    	}t[N*50]; 
    	void pushup(int x) {
    		t[x].sum=t[ls].sum+t[rs].sum;    
    	}
    	void update(int &x,int l,int r,int p,int v) {
    		if(!x) 
    			x=++tot; 
    		if(l==r) {
    			t[x].sum=v; 
    			return;
    		} 
    		int mid=(l+r)>>1;    
    		if(p<=mid) update(ls,l,mid,p,v); 
    		else update(rs,mid+1,r,p,v); 
    		pushup(x);  
    	}
    	int query(int x,int l,int r,int L,int R) {
    		if(!x) return 0; 
    		if(l>=L&&r<=R) return t[x].sum; 
    		int mid=(l+r)>>1,re=0; 
    		if(L<=mid) re+=query(ls,l,mid,L,R); 
    		if(R>mid) re+=query(rs,mid+1,r,L,R);  
    		return re; 
    	}
    	int merge(int l,int r,int x,int y,int tt) { 
    		if(!x||!y) 
    			return x+y;  
    		int oo=++tot;      
    		if(l==r) 
    			t[oo].sum=1;                 
    		else { 
    			int mid=(l+r)>>1; 
    			t[oo].lson=merge(l,mid,t[x].lson,t[y].lson,tt); 
    			t[oo].rson=merge(mid+1,r,t[x].rson,t[y].rson,tt); 
    			pushup(oo);   
    		} 
    		return oo; 
    	}
    	#undef ls
    	#undef rs
    }; 
    namespace SAM {  
    	int tot,id[N<<1],tax[N<<1],rk[N<<1];      
    	struct Node {  
    		map<int,int>ch; 
    		int len,pre; 
    	}t[N<<1];            
    	struct Edge { 
    		int from,c;      
    		Edge(int from=0,int c=0):from(from),c(c){} 
    	};             
    	queue<int>q; 
    	int extend(int lst,int c,int i) {      
    		int p=lst; 
    	    if(t[p].ch.count(c)) { 
    	    	int q=t[p].ch[c]; 
    	    	if(t[q].len==t[p].len+1) seg::update(rt[q],1,Trie::tim,i,1),lst=q; 
    	    	else { 
    	    		int nq=++tot; 
    	    		t[nq].len=t[p].len+1;  
    	    		t[nq].pre=t[q].pre,t[q].pre=nq;     
    	    		t[nq].ch=t[q].ch; 
    	    		seg::update(rt[nq],1,Trie::tim,i,1);      
    	    		for(;p&&t[p].ch.count(c)&&t[p].ch[c]==q;p=t[p].pre) t[p].ch[c]=nq;      
    	    		lst=nq;            
    	    	}
    	    } 
    	    else { 
    	    	int np=++tot;  
    	    	t[np].len=t[p].len+1;  
    	    	seg::update(rt[np],1,Trie::tim,i,1);    
    	    	for(;p&&!t[p].ch.count(c);p=t[p].pre) t[p].ch[c]=np;   
    	    	if(!p) t[np].pre=1;  
    	        else {
    	        	int q=t[p].ch[c]; 
    	        	if(t[q].len==t[p].len+1) t[np].pre=q;     
    	        	else { 
    	        		int nq=++tot; 
    	        		t[nq].len=t[p].len+1; 
    	        		t[nq].pre=t[q].pre,t[q].pre=t[np].pre=nq;         
    	        		t[nq].ch=t[q].ch; 
    	        		for(;p&&t[p].ch.count(c)&&t[p].ch[c]==q;p=t[p].pre) t[p].ch[c]=nq;  
    	        	}
    	        }
    	        lst=np; 
    	    }  
    	    return lst;  
    	}  
    	void construct() {
    		int i,j; 
    		id[0]=tot=1;   
    		q.push(0);    
    		while(!q.empty()) {
    			int u=q.front();q.pop(); 
    			for(i=0;i<27;++i) {
    				if(Trie::t[u].ch.count(i)) { 
    					int v=Trie::t[u].ch[i];   
    					q.push(v);               
    					id[v]=extend(id[u],i,Trie::t[v].dfn);        
    				}
    			}
    		} 
    		for(i=1;i<=tot;++i) tax[i]=0; 
    		for(i=1;i<=tot;++i) ++tax[t[i].len];    
    		for(i=1;i<=tot;++i) tax[i]+=tax[i-1];        
    		for(i=1;i<=tot;++i) rk[tax[t[i].len]--]=i;   
    		for(i=tot;i>1;--i) { 
    			int u=rk[i];     
    			int ff=t[u].pre;   
    			if(ff>1) 
    				rt[ff]=seg::merge(1,Trie::tim,rt[u],rt[ff],0); 
    		}
    	} 
    }; 
    void solve(int x) { 
    	int p=1,i,len=strlen(S+1),re=0; 
    	for(i=1;i<=len;++i) {
    		int c=S[i]-'a'; 
    		if(SAM::t[p].ch.count(c)) 
    			p=SAM::t[p].ch[c]; 
    		else {
    			printf("0
    "); 
    			return; 
    		}
    	}    
    	for(x=Trie::id[x];x;x=Trie::t[Trie::t[x].top].fa) {  
    	    re+=seg::query(rt[p],1,Trie::tim,Trie::t[Trie::t[x].top].dfn,Trie::t[x].dfn);       
    	}
    	printf("%d
    ",re);     
    }
    int main() {
    	int i,j; 
    	// setIO("input"); 
    	scanf("%d",&n); 
    	for(i=1;i<=n;++i) {
    		int op,lst=0; 
    		char str[3];  
    		scanf("%d",&op);      
    		if(op==2) scanf("%d",&lst),lst=Trie::id[lst]; 
    		scanf("%s",str);   
    		if(!Trie::t[lst].ch.count(str[0]-'a')) {  
    			Trie::t[lst].ch[str[0]-'a']=i; 
    			Trie::id[i]=i;        
    		}
    		else Trie::id[i]=Trie::t[lst].ch[str[0]-'a'];   
    	} 
    	Trie::build_tree();   
    	SAM::construct();     
    	scanf("%d",&m);   
    	for(i=1;i<=m;++i) 
    		scanf("%d%s",&j,S+1), solve(j);       
    	return 0; 
    }
    

      

  • 相关阅读:
    Spring类中的split()方法
    单例模式
    c#操作文件
    c#选择文件
    c#判断程序是否正在运行
    .net创建文件夹和txt文件
    .net 程序睡眠之后执行
    .net读取txt文件
    .net Post Json数据
    sql查看表结构以及表说明
  • 原文地址:https://www.cnblogs.com/guangheli/p/11414081.html
Copyright © 2011-2022 走看看