题解 P5043 【【模板】树同构([BJOI2015]树的同构)】
P5043 树同构
这题我用了相对准确的最小表示法来完成(与某个取模 Hash 对拍时,那个 Hash 炸得很惨,不知道是否是写法原因)。
另一篇题解也是最小表示,但是我的实现方式不同。
一、有根树的最小表示
树的括号序列转化:从树根开始执行一次 DFS,每一个结点的子树都有进入和回溯两个过程。DFS 过程中维护一个序列,进入一个子树时向序列中加入一个左括号,回溯时向序列中加入一个右括号,如此可以构成长度为
然而 DFS 过程中,一个点
最小表示法的性质:
两颗有根树树同构的充要条件是其最小表示相同。
证明:
充分性:通过最小表示可以唯一确定一棵树的结构:从根节点出发,遇到左括号就向下走一步,遇到右括号就向上走一步,这样的走法在结点无标号时是确定的。树的结构显然唯一确定,因此最小表示相同,树的结构就相同。
必要性:结构相同,DFS 过程完全相同,最小表示显然相同。
最小表示的求法:
DFS 一次即可,设
字符之间直接连接。
最坏时间复杂度为
二、无根树的同构
一种简单粗暴的方法:对于树
这个方法的原理其实是无根树转成有根树,令
该算法完成 这题 的时间复杂度是
对于两棵同构树
通常我们选择的同位点是重心。
对于树
因此对于每棵树,我们只需要最多检验两个点的最小表示,时间复杂度变成
#include <bits/stdc++.h>
using namespace std;
const int MAXN=60;
int t,n,x,eg,mnp,flg,hd[MAXN],ver[2*MAXN],nx[2*MAXN],sz[MAXN],mx[MAXN];
string mn[MAXN],f[MAXN],g[MAXN],tmp;
void add_edge (int x,int y) {
ver[++eg]=y;
nx[eg]=hd[x];
hd[x]=eg;
return;
}
void dfs1 (int x,int fa) {
sz[x]=1;
for (int i=hd[x];i;i=nx[i]) {
if (ver[i]==fa) {continue;}
dfs1(ver[i],x);
sz[x]+=sz[ver[i]];
mx[x]=max(mx[x],sz[ver[i]]);
}
mx[x]=max(mx[x],n-sz[x]);
mnp=min(mnp,mx[x]);
return;
}
void dfs2 (int x,int fa) {
f[x]="0";
for (int i=hd[x];i;i=nx[i]) {
if (ver[i]==fa) {continue;}
dfs2(ver[i],x);
}
int tot=0;
for (int i=hd[x];i;i=nx[i]) {
if (ver[i]==fa) {continue;}
g[++tot]=f[ver[i]];
}
sort(g+1,g+tot+1);
for (int i=1;i<=tot;i++) {f[x]+=g[i];}
f[x]+="1";
return;
}
int main () {
scanf("%d",&t);
for (int ii=1;ii<=t;ii++) {
scanf("%d",&n);
memset(hd,0,sizeof(hd));
memset(mx,0,sizeof(mx));
eg=flg=0,mnp=n+1;
tmp="1";
for (int i=1;i<=n;i++) {
scanf("%d",&x);
if (x) {add_edge(x,i),add_edge(i,x);}
}
dfs1(1,0);
for (int i=1;i<=n;i++) {
if (mx[i]==mnp) {
dfs2(i,0);
tmp=min(tmp,f[i]);
}
}
mn[ii]=tmp;
for (int i=1;i<=ii-1;i++) {
if (mn[i]==mn[ii]) {
printf("%d\n",i),flg=1;
break;
}
}
if (!flg) {printf("%d\n",ii);}
}
return 0;
}