●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\n",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\n",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\n%d\n",SUF.Count(),SUF.size);
	return 0;
}

  

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

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

原文地址:https://www.cnblogs.com/zj75211/p/8541852.html

时间: 2024-10-12 03:19:33

●BZOJ 3926 [Zjoi2015]诸神眷顾的幻想乡的相关文章

字符串(广义后缀自动机):BZOJ 3926 [Zjoi2015]诸神眷顾的幻想乡

3926: [Zjoi2015]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 843  Solved: 510[Submit][Status][Discuss] Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有n

BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡

3926: [Zjoi2015]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1017  Solved: 599[Submit][Status][Discuss] Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有

BZOJ 3926 Zjoi2015 诸神眷顾的幻想乡 后缀自动机

题目大意:给定一棵树,每个节点有一个字符,求从一个节点出发沿最短路径走到另一个节点所构成的字符串一共有多少种 此生无悔入东方,来世愿生幻想乡 题目戳这里 注意一句话:太阳花田的结构比较特殊,只与一个空地相邻的空地的数量不超过20个 有奖问答:↑你看到这句话的第一反应是啥? 1.度数<=20 2.叶节点数<=20 仔细看几遍就能找到答案~ [捂脸熊]陈老师真是语文高手.... 叶节点数<=20还做啥了... 直接从每个叶节点DFS一遍,然后构建广义后缀自动机,最终答案就是每个节点的深度-p

BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机 后缀自动机 字符串

https://www.lydsy.com/JudgeOnline/problem.php?id=3926 广义后缀自动机是一种可以处理好多字符串的一种数据结构(不像后缀自动机只有处理一到两种的时候比较方便). 后缀自动机可以说是一种存子串的缩小点数的trie树,广义后缀自动机就是更改了一下塞点的方式让它可以塞多个子串. 1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<

BZOJ 3926: [Zjoi20150]诸神眷顾的幻想乡

3926: [Zjoi20150]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 438  Solved: 273 Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有n块空地.在过去,幽香为了方便,在这n块空地之间修

bzoj 3926 [Zjoi20150]诸神眷顾的幻想乡(SAM)

3926: [Zjoi20150]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 615  Solved: 369[Submit][Status][Discuss] Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有

BZOJ 3926: [Zjoi20150]诸神眷顾的幻想乡(后缀自动机)

被这道题坑了= =只与一个空地相连的空地不超过20个只与一个空地相连的空地不超过20个 因为很重要所以说两遍 就是说儿子节点最多只有20个 把这20个节点作为根遍历一遍所得到的tire所得到的所有不同子串就是答案了 怎么求? 这可是CLJ出的啊 想想她讲过什么 后缀自动机或可持久化后缀数组的经典应用 由于不会打可持久化后缀数组,就打了个自动机 自己对后缀自动机根本不熟,找时间在多做几道题 CODE: #include<cstdio> #include<iostream> #incl

3926: [Zjoi2015]诸神眷顾的幻想乡

传送门 一个广义后缀自动机模板. //Achen #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<vector> #include<cstdio> #include<queue> #include<cmath> const int N=4000007; typedef long long LL

【BZOJ 3926】 [Zjoi2015]诸神眷顾的幻想乡 (广义SAM)

3926: [Zjoi2015]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 974  Solved: 573 Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有n块空地.在过去,幽香为了方便,在这n块空地之间修建