【BJOI 2015】树的同构

【题目】

传送门

Description

树是一种很常见的数据结构。

我们把 n n n 个点, n − 1 n-1 n1 条边的连通无向图称为树。

若将某个点作为根,从根开始遍历,则其它的点都有一个前驱,这个树就成为有根树。

对于两个树 t 1 t1 t1 t 2 t2 t2,如果能够把树 t 1 t1 t1 的所有点重新标号,使得树 t 1 t1 t1 和树 t 2 t2 t2 完全相同,那么这两个树是同构的。也就是说,它们具有相同的形态。

现在,给你 m m m 个有根树,请你把它们按同构关系分成若干个等价类。

Input

第一行,一个整数 m m m

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

Output

输出 m m 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 1, 2, 4 1,2,4 的树是同构的。编号为 3 3 3 的树只与它自身同构。
100 % 100\% 100% 的数据中, 1 1 1 n , m n, m n,m 50 50 50


【分析】

首先来说一下括号序列吧

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

那么对于这道题,其实只用找到括号序列就行了

现在又有两个问题:

  • 对于两棵树,如果选的根不一样,就算结构相同,括号序列不一定相同
  • 对于两棵树,如果选的根相同,但若 d f s dfs dfs 的顺序不同,得到的括号序列也不一定相同

所以现在的问题就是如何找以及如何进行 d f s dfs dfs

对于找根,由于一颗是树的重心至多两个,而若两棵树同构,重心肯定是一样的,所以就从重心开始 d f s dfs dfs

对于 d f s dfs dfs,每次 d f s dfs dfs 到一个节点,都把它的所有子树的括号序列排序,然后按照顺序加进根的括号序列

听说用 v e c t o r vector vector 会好一点,不过直接用 s t r i n g string string 也可以过,毕竟 n,m 最大才 50


【代码】

#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#define N 1005
#define inf 0x3f3f3f3f
using namespace std;
int n,m,t,num;
int Max[N],size[N];
int first[N],v[N],nxt[N];
string rec[N],brackets[N];
void add(int x,int y)
{
	t++;
	nxt[t]=first[x];
	first[x]=t;
	v[t]=y;
}
void find(int x,int father)
{
	int i,j;
	Max[x]=0,size[x]=1;
	for(i=first[x];i;i=nxt[i])
	{
		j=v[i];
		if(j!=father)
		{
			find(j,x);
			size[x]+=size[j];
			Max[x]=max(Max[x],size[j]);
		}
	}
	Max[x]=max(Max[x],n-size[x]);
	num=min(num,Max[x]);
}
void dfs(int x,int father)
{
	int i,j,num=0;
	string temp[N];
	brackets[x]='(';
	for(i=first[x];i;i=nxt[i])
	{
		j=v[i];
		if(j!=father)
		{
			dfs(j,x);
			temp[++num]=brackets[j];
		}
	}
	sort(temp+1,temp+num+1);
	for(i=1;i<=num;++i)
	  brackets[x]+=temp[i];
	brackets[x]+=')';
}
int main()
{
	int i,j,x;
	scanf("%d",&m);
	for(i=1;i<=m;++i)
	{
		t=0;
		scanf("%d",&n);
		memset(first,0,sizeof(first));
		for(j=1;j<=n;++j)
		{
			scanf("%d",&x);
			if(x)  add(x,j),add(j,x);
		}
		num=inf,find(1,0);
		string s=" ";
		for(j=1;j<=n;++j)
		{
			if(Max[j]==num)
			{
				dfs(j,0);
				if(s==" "||s>brackets[j])
				  s=brackets[j];
			}
		}
		rec[i]=s;
		for(j=1;j<=i;++j)
		{
			if(s==rec[j])
			{
				printf("%d\n",j);
				break;
			}
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值