zoukankan      html  css  js  c++  java
  • ●BZOJ 3926 [Zjoi2015]诸神眷顾的幻想乡

    题链:

    http://www.lydsy.com/JudgeOnline/problem.php?id=3926
    题解&&代码:

    后缀自动机,Trie树


    如果以每个叶子为根,所有的子串一定在某一颗树的一条由祖先到子孙的链上。
    由于叶子节点只有不超过20个,那么就可以从每个叶子开始dfs,把每个从根开始的串都加入一颗trie树。
    显然,所有的子串都在trie树上,那么现在就需要统计trie树上有多少不同的子串。
    对trie树建立后缀自动机,然后统计不同的子串个数即可。
    (本人不会在线建立trie树的后缀自动机,所以就写了一个离线BFS trie树建后缀自动机)

    #include<bits/stdc++.h>
    #define MAXN 100005
    #define ll long long
    using namespace std;
    ll cnt[MAXN*20];
    int color[MAXN],N,C;
    struct Edge{
    	int ent;
    	int to[MAXN*2],nxt[MAXN*2],head[MAXN];
    	Edge(){ent=2;}
    	void Adde(int u,int v){
    		to[ent]=v; nxt[ent]=head[u]; head[u]=ent++;
    		to[ent]=u; nxt[ent]=head[v]; head[v]=ent++;
    	}
    }E;
    struct Trie{
    	int size;
    	int ch[MAXN*20][10];
    	int Trans(int last,int x){
    		if(ch[last][x]) return ch[last][x];
    		return ch[last][x]=++size;
    	}
    	void Reset(){size=1;}
    }T;
    struct SAM{
    	int size;
    	int maxs[MAXN*20],trans[MAXN*20][10],parent[MAXN*20];
    	int Newnode(int a,int b){
    		++size; maxs[size]=a;
    		memcpy(trans[size],trans[b],sizeof(trans[b]));
    		return size;
    	}
    	int Extend(int last,int x){
    		static int p,np,q,nq;
    		p=last; np=Newnode(maxs[p]+1,0);
    		for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np;
    		if(!p) parent[np]=1;
    		else{
    			q=trans[p][x];
    			if(maxs[p]+1!=maxs[q]){
    				nq=Newnode(maxs[p]+1,q);
    				parent[nq]=parent[q];
    				parent[q]=parent[np]=nq;
    				for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq;
    			}
    			else parent[np]=q;
    		}
    		return np;
    	}
    	void Reset(){
    		memset(trans[0],0,sizeof(trans[0]));
    		size=0; Newnode(0,0);
    	}
    	void Count(){
    		static queue<int>Q;
    		static int order[MAXN*20],in[MAXN*20],ont;
    		for(int p=1;p<=size;p++)
    			for(int c=0;c<10;c++) if(trans[p][c])
    				in[trans[p][c]]++;
    		Q.push(1);
    		while(!Q.empty()){
    			int p=Q.front(); Q.pop(); order[++ont]=p;
    			for(int c=0;c<10;c++) if(trans[p][c]){
    				in[trans[p][c]]--;
    				if(!in[trans[p][c]]) Q.push(trans[p][c]);
    			}
    		}
    		for(int i=size,p;i;i--){
    			p=order[i]; cnt[p]=(p==1?0:1);
    			for(int c=0;c<10;c++) if(trans[p][c])
    				cnt[p]+=cnt[trans[p][c]];
    		}
    	}
    }SUF;
    void dfs(int u,int dad,int p){
    	p=T.Trans(p,color[u]);
    	for(int i=E.head[u];i;i=E.nxt[i])
    		if(E.to[i]!=dad) dfs(E.to[i],u,p);
    }
    void bfs(){
    	static int state[MAXN*20];
    	static queue<int>Q; 
    	Q.push(1); state[1]=1;
    	while(!Q.empty()){
    		int u=Q.front(); Q.pop();
    		for(int c=0;c<10;c++) if(T.ch[u][c]){
    			state[T.ch[u][c]]=SUF.Extend(state[u],c);
    			Q.push(T.ch[u][c]);
    		}
    	}
    }
    int main(){
    	//freopen("substring.in","r",stdin);
    	//freopen("substring.out","w",stdout);
    	static int in[MAXN];
    	scanf("%d%d",&N,&C);
    	SUF.Reset(); T.Reset();
    	for(int i=1;i<=N;i++) scanf("%d",&color[i]);
    	for(int i=1,a,b;i<N;i++)
    		scanf("%d%d",&a,&b),E.Adde(a,b),in[a]++,in[b]++;
    	for(int i=1;i<=N;i++) if(in[i]==1) dfs(i,0,1);
    	bfs();
    	SUF.Count(); 
    	printf("%lld
    ",cnt[1]);
    	return 0;
    }
    

      

    然后看了别人的做法,发现利用后缀自动机里面每个状态的不重复的性质性,还可以有更简便的求不同子串个数的方法(SUF.Count()有变化,效率提升了些)。

    #include<bits/stdc++.h>
    #define MAXN 100005
    #define ll long long
    using namespace std;
    ll cnt;
    int color[MAXN],N,C;
    struct Edge{
    	int ent;
    	int to[MAXN*2],nxt[MAXN*2],head[MAXN];
    	Edge(){ent=2;}
    	void Adde(int u,int v){
    		to[ent]=v; nxt[ent]=head[u]; head[u]=ent++;
    		to[ent]=u; nxt[ent]=head[v]; head[v]=ent++;
    	}
    }E;
    struct Trie{
    	int size;
    	int ch[MAXN*20][10];
    	int Trans(int last,int x){
    		if(ch[last][x]) return ch[last][x];
    		return ch[last][x]=++size;
    	}
    	void Reset(){size=1;}
    }T;
    struct SAM{
    	int size;
    	int maxs[MAXN*20],trans[MAXN*20][10],parent[MAXN*20];
    	int Newnode(int a,int b){
    		++size; maxs[size]=a;
    		memcpy(trans[size],trans[b],sizeof(trans[b]));
    		return size;
    	}
    	int Extend(int last,int x){
    		static int p,np,q,nq;
    		p=last; np=Newnode(maxs[p]+1,0);
    		for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np;
    		if(!p) parent[np]=1;
    		else{
    			q=trans[p][x];
    			if(maxs[p]+1!=maxs[q]){
    				nq=Newnode(maxs[p]+1,q);
    				parent[nq]=parent[q];
    				parent[q]=parent[np]=nq;
    				for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq;
    			}
    			else parent[np]=q;
    		}
    		return np;
    	}
    	void Reset(){
    		memset(trans[0],0,sizeof(trans[0]));
    		size=0; Newnode(0,0);
    	}
    	void Count(){
    		for(int p=1;p<=size;p++) cnt+=maxs[p]-maxs[parent[p]];
    	}
    }SUF;
    void dfs(int u,int dad,int p){
    	p=T.Trans(p,color[u]);
    	for(int i=E.head[u];i;i=E.nxt[i])
    		if(E.to[i]!=dad) dfs(E.to[i],u,p);
    }
    void bfs(){
    	static int state[MAXN*20];
    	static queue<int>Q; 
    	Q.push(1); state[1]=1;
    	while(!Q.empty()){
    		int u=Q.front(); Q.pop();
    		for(int c=0;c<10;c++) if(T.ch[u][c]){
    			state[T.ch[u][c]]=SUF.Extend(state[u],c);
    			Q.push(T.ch[u][c]);
    		}
    	}
    }
    int main(){
    //	freopen("substring.in","r",stdin);
    //	freopen("substring.out","w",stdout);
    	static int in[MAXN];
    	scanf("%d%d",&N,&C);
    	SUF.Reset(); T.Reset();
    	for(int i=1;i<=N;i++) scanf("%d",&color[i]);
    	for(int i=1,a,b;i<N;i++)
    		scanf("%d%d",&a,&b),E.Adde(a,b),in[a]++,in[b]++;
    	for(int i=1;i<=N;i++) if(in[i]==1) dfs(i,0,1);
    	bfs();
    	SUF.Count(); 
    	printf("%lld
    ",cnt);
    	return 0;
    }
    

      

    然后想去学学在线对trie树建立后缀自动机,但是论文看得我脑袋疼。。。
    这时突然发现其他博主的代码也是可以在线增量的,似乎叫广义后缀自动机。。。,
    感觉看代码的实现似乎没毛病,而且还避免了建trie树。只是出现了一些无法到达的状态。

    #include<bits/stdc++.h>
    #define MAXN 100005
    #define ll long long
    using namespace std;
    int N,C;
    int color[MAXN];
    struct Edge{
    	int to[MAXN*2],nxt[MAXN*2],head[MAXN],ent;
    	Edge(){ent=2;}
    	void Adde(int u,int v){
    		to[ent]=v; nxt[ent]=head[u]; head[u]=ent++;
    		to[ent]=u; nxt[ent]=head[v]; head[v]=ent++;
    	}
    }E;
    struct SAM{
    	int size;
    	int maxs[MAXN*20],trans[MAXN*20][26],parent[MAXN*20];
    	int Newnode(int a,int b){
    		++size; maxs[size]=a;
    		memcpy(trans[size],trans[b],sizeof(trans[b]));
    		return size;
    	}
    	int Extend(int last,int x){
    		static int p,np,q,nq; p=last;
    		if(trans[p][x]&&maxs[p]+1==maxs[trans[p][x]]) return trans[p][x];
    		np=Newnode(maxs[p]+1,0);
    		for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np;
    		if(!p) parent[np]=1;
    		else{
    			q=trans[p][x];
    			if(maxs[p]+1!=maxs[q]){
    				nq=Newnode(maxs[p]+1,q);
    				parent[nq]=parent[q];
    				parent[q]=parent[np]=nq;
    				for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq;
    			}
    			else parent[np]=q;
    		}
    		return np;
    	}
    	void Reset(){
    		memset(trans[0],0,sizeof(trans[0]));
    		size=0; Newnode(0,0);
    	}
    	ll Count(ll cnt=0){
    		for(int p=1;p<=size;p++) cnt+=maxs[p]-maxs[parent[p]];
    		return cnt;
    	}
    }SUF;
    void dfs(int u,int dad,int last){
    	last=SUF.Extend(last,color[u]);
    	for(int i=E.head[u];i;i=E.nxt[i])
    		if(E.to[i]!=dad) dfs(E.to[i],u,last);
    }
    int main(){
    	freopen("substring.in","r",stdin);
    	//freopen("substring.out","w",stdout);
    	static int in[MAXN];
    	SUF.Reset();
    	scanf("%d%d",&N,&C);
    	for(int i=1;i<=N;i++) scanf("%d",&color[i]);
    	for(int i=1,u,v;i<N;i++)
    		scanf("%d%d",&u,&v),E.Adde(u,v),in[u]++,in[v]++;
    	for(int i=1;i<=N;i++) if(in[i]==1) dfs(i,0,1);
    	printf("%lld
    %d
    ",SUF.Count(),SUF.size);
    	return 0;
    }
    

      

    结果是比之前建了trie树的离线自动机构法多了一些状态,
    (第20组数据做的测试,相比于建立Trie树后再离线bfs建后缀自动机多了2w个状态,估计就是那些无法到达的状态产生的)

    然后我想:“如果还是先建立一颗trie树,再用上面的增量法去在线构造Trie树的后缀自动机,会不会减少一些无法到达的状态?”
    试了一下,结果更慢了。。。(第20组数据做的测试,相比与不建Trie树直接在线增量法构造后缀自动机,多了1ow个状态。。。)
    就不放代码了。

  • 相关阅读:
    Ant编译android程序
    android系统短信库的一些用法
    Android APK 签名机制
    android调用照相机拍照获取照片并做简单剪裁
    调用Android系统“应用程序信息(Application Info)”界面
    Speex manul中文版
    Android amr语音编解码解惑
    android Notification 的使用
    ContentProvider和Uri详解
    深入理解SetUID
  • 原文地址:https://www.cnblogs.com/zj75211/p/8541852.html
Copyright © 2011-2022 走看看