树同构模板(树哈希)
树同构模板(树哈希)
时间复杂度:\(O(MN\log N)\)
#include<bits/stdc++.h>
using namespace std;
const int MAXN=55;
const int mod=1e9+7,base=19491001;//模数(防止哈希值溢出);基数(用于多项式哈希)
int M,N,Pow[MAXN],sz[MAXN],hd[MAXN],ver[MAXN*2],nxt[MAXN*2],tot;//Pow[i]=base^i%mod,预处理幂次
int dep[MAXN],rt,rrt,rtm,has[MAXN];//节点深度(以当前重心为根);rt:一个重心;rrt:可能的第二个重心;rtm:用于寻找重心的最小最大子树;has[u]表示以u为根的子树的哈希值
pair<int,int> tr[MAXN];//存储每棵树的特征对(A,B),A>=B
void add(int u,int v){
ver[++tot]=v;nxt[tot]=hd[u];hd[u]=tot;
ver[++tot]=u;nxt[tot]=hd[v];hd[v]=tot;
}
void Find(int u,int fa){//第一次 DFS:计算子树大小,同时找重心
sz[u]=1;
int maxn=0;//记录u的所有子树中最大的大小
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa) continue;
Find(v,u);
sz[u]+=sz[v];//累加子树大小
maxn=max(maxn,sz[v]);//更新最大子树
}
maxn=max(maxn,N-sz[u]);//还要考虑“父方向”的连通块大小
//重心的条件:最大连通块不超过N/2
if(maxn<rtm){//找到更优的重心候选
rtm=maxn;
rt=u;rrt=0;//记录重心;清空第二个重心
}else if(maxn==rtm){//与当前最优相等,可能存在第二个重心
rrt=u;
}
}
int dfs(int u,int fa){//第二次DFS:计算以u为根的子树的哈希值(有根树哈希);返回哈希值,同时将全局has[u]更新
has[u]=1ll*dep[u]*Pow[1]%mod;//1.基础哈希:深度*G;深度参与是为了区分不同层的节点,防止仅结构相同但深度不同的子树被误判为相同
sz[u]=1;//重新计算子树大小(此处与Find中的sz独立,因为我们更换了根)
for(int i=hd[u];i;i=nxt[i]){//先递归计算所有子节点的哈希值
int v=ver[i];
if(v==fa) continue;
dep[v]=dep[u]+1;//深度=父节点深度+1
dfs(v,u);//递归后has[v]和sz[v]已求出
}
//2.收集所有子树的(哈希值,子树大小)对
pair<int,int> sav[N];//局部数组
int cnt=0;//子树个数
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa) continue;
sav[++cnt]=make_pair(has[v],sz[v]);
}
//3.对子树信息排序:先按哈希值,再按大小;必须排序,因为我们要处理的是无序树(子节点顺序无关)
sort(sav+1,sav+cnt+1);
//4.多项式哈希合并:将排序后的子树信息依次并入当前节点的哈希
//公式:hash=base_hash+Σ(child_hash*base^{current_size});这里current_size是合并该子树之前已经累积的节点数(包含自身和已合并的子树)
//这样保证了即使两棵子树哈希相同,若它们处于不同的位置(累积大小不同),也会被区分开
for(int i=1;i<=cnt;i++){
has[u]=(has[u]+1ll*sav[i].first*Pow[sz[u]])%mod;
sz[u]+=sav[i].second;//累加该子树的大小
}
return has[u];
}
pair<int,int> trhash(){//计算一棵无根树的特征对(A,B)
rtm=mod;rrt=0;//初始化用于寻找重心
Find(1,0);//任选起点(1)进行第一次DFS找重心
int A=0,B=0;
//以第一个重心为根计算有根树哈希
dep[rt]=1;
A=dfs(rt,0);
if(rrt){//存在第二个重心
dep[rrt]=1;
B=dfs(rrt,0);
}
if(A<B) swap(A,B);//保证A>=B,这样(A,B)唯一,不受重心顺序影响
return {A,B};
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
Pow[0]=1;
for(int i=1;i<=50;i++) Pow[i]=1ll*Pow[i-1]*base%mod;//预处理
cin>>M;
for(int t=1;t<=M;t++){
cin>>N;
tot=0;
memset(hd,0,sizeof hd);
for(int i=1,fa;i<=N;i++){
cin>>fa;
if(fa) add(fa,i);//建无向边
}
tr[t]=trhash();//计算这棵无根树的特征对,并保存
//在线性同构集合中寻找最早出现的同构树编号
int ans=t;
for(int j=1;j<t;j++){
if(tr[j]==tr[t]){//特征对完全相同即判定同构
ans=j;break;
}
}
cout<<ans<<'\n';
}
return 0;
}

浙公网安备 33010602011771号