1021 - 括号序列+树的重心 - 树的同构(BZOJ 4337)

BJOI2015 树的同构

Description

树是一种很常见的数据结构。
我们把N个点,N-1条边的连通无向图称为树。
若将某个点作为根,从根开始遍历,则其它的点都有一个前驱,这个树就成为有根树。
对于两个树T1和T2,如果能够把树T1的所有点重新标号,使得树T1和树T2完全相
同,那么这两个树是同构的。也就是说,它们具有相同的形态。
现在,给你M个有根树,请你把它们按同构关系分成若干个等价类。

Input

第一行,一个整数M。
接下来M行,每行包含若干个整数,表示一个树。第一个整数N表示点数。接下来N
个整数,依次表示编号为1到N的每个点的父亲结点的编号。根节点父亲结点编号为0。

Output

输出M行,每行一个整数,表示与每个树同构的树的最小编号。

Sample Input

4

4 0 1 1 2

4 2 0 2 3

4 0 1 1 1

4 0 1 2 3

Sample Output

1

1

3

1

HINT

【样例解释】

编号为1, 2, 4 的树是同构的。编号为3 的树只与它自身同构。

100% 的数据中,1 ≤ N, M ≤ 50。

分析

在讲这道题之前,我们先来了解一下括号序列

考虑 dfs 序,在 dfs 完一个点的时候,我们同样把他加入序列,这样得
到的序列叫做括号序列。
对于一棵有根树,如果子节点有序,那么这棵树唯一对应着一个括号序
列。


显然,对于这道题,给出的每一棵树,如果我们把不同的节点作为根,往下dfs,那么就算这两棵树的形态一样,我们得到的括号序列也不相同
退一步,就算我们从同一个节点出发,如果dfs的顺序不一样,最后得到的括号序列也不相同
这提示我们本题的关键(判断两棵树是否同构)就在于选对根和遍历顺序

又因为对于一个括号序列,我们发现,每一个大括号里面包含的次级括号表示的是它子树的遍历情况,而且整体交换次级括号的顺序不会影响树的形态,所以我们考虑将每个子树遍历得到的括号序列排个序,这就是伪的最小表示法,现在遍历顺序解决了,考虑根。
我们很容易想到,一棵树的重心最多只有两个,而两棵树如果同构则其重心肯定是同一(两)个。但由于这道题的范围只有50,听说有人直接枚举,将所有的点都作为根,来遍历一遍,都可以卡过去

代码

#include<bits/stdc++.h>
#define N 100
using namespace std;
int m,n,maxn;
int sze[N],son[N];
int nxt[N<<1],head[N],to[N<<1],cnt=0;
string ans[N<<1],h[N<<1];
void add(int x,int y){
	nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;
	nxt[++cnt]=head[y];head[y]=cnt;to[cnt]=x;
} 
void getG(int u,int fu){
	sze[u]=1;son[u]=0;
	for(int e=head[u];e;e=nxt[e]){
		int v=to[e];
		if(v==fu) continue;
		getG(v,u);
		sze[u]+=sze[v];
		if(sze[v]>son[u]) son[u]=sze[v];
	}
	son[u]=max(son[u],n-sze[u]);
	maxn=min(maxn,son[u]);
}
void dfs(int u,int fu){
	h[u]="(";
	vector<string> V;
	for(int e=head[u];e;e=nxt[e]){
		int v=to[e];
		if(v==fu) continue;
		dfs(v,u);
		V.push_back(h[v]);
	}
	sort(V.begin(),V.end());
	for(vector<string> ::iterator it=V.begin();it!=V.end();++it){
		h[u]+=*it;
	}
	h[u]+=")";
}
int main(){
	scanf("%d",&m);
	int i,j,t;
	for(t=1;t<=m;++t){
		scanf("%d",&n);
		cnt=0;memset(head,0,sizeof(head));
		for(i=1;i<=n;++i){
			scanf("%d",&j);
			if(j) add(i,j);
		}
		maxn=n;
		getG(1,0);
		string tmp=" ";
		for(i=1;i<=n;++i){
			if(son[i]==maxn){
				dfs(i,0);
				if(h[i]<tmp||tmp==" ") tmp=h[i];
			}
		}
		ans[t]=tmp;
		for(i=1;i<=t;++i){
			if(ans[i]==ans[t]) {
				printf("%d\n",i);
				break;
			}
		}
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值