树同构模板(树哈希)

题目

树同构模板(树哈希)

时间复杂度:\(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;
}
posted @ 2026-08-10 21:52  decentz  阅读(9)  评论(0)    收藏  举报