我们有时需要判断一些树是否同构。这时,选择恰当的Hash方式来将树映射成一个便于储存的Hash值(一般是 32 位或 64 位整数)是一个优秀的方案。
树Hash定义在有根树上。判断无根树同构的时候,可以比较重心为根的Hash值(一个树最多有两个根)或者比较每个点为根的Hash值(后者有O(n)的求解方法)。
本文采用的Hash方式如下:
(f_{x}=1+sum_{y in s o n_{x}} f_{y} imes operatorname{prime}left(operatorname{size}_{y} ight))
模板
//dfs1:求一个点为根时所有点子树的哈希值;
//dfs2:求每个点为根时该点的哈希值;
struct Tree{
struct edge{
int v,next;
}E[maxm];
int head[maxn],tot;
void addedge(int u,int v){
E[++tot].v=v;
E[tot].next=head[u];
head[u]=tot;
}
ll hx[maxn],sz2[maxn];
void dfs1(int u,int fa){
sz2[u]=hx[u]=1;
for(int i=head[u];i;i=E[i].next){
int v=E[i].v;
if(v==fa)continue;
dfs1(v,u);
hx[u]+=hx[v]*Prime[sz2[v]];
sz2[u]+=sz2[v];
}
}
ll ghx[maxn];
void dfs2(int u,int fa,ll faf,int n){//faf:fa除去u子树,剩余树的哈希值
ghx[u]=hx[u]+faf*(Prime[n-sz2[u]]);
faf*=Prime[n-sz2[u]];//此时,faf=以u为根fa方向的树的哈希值-1
for(int i=head[u];i;i=E[i].next){
int v=E[i].v;
if(v!=fa){
dfs2(v,u,faf+hx[u]-hx[v]*(Prime[sz2[v]]),n);//hx[u]-hx[v]*(Prime[size[v]):以u为根,除去fa和v方向的子树,剩余的哈希值
}
}
}
}
例题
P5043无根树同构
题意:按顺序给出一些树,输出在这之前和该树同构的最早出现的树
解法:找出每棵树的重心(最多两个),该树的哈希值就是以两个重心为根的哈希值的最大值(或者最小值)
//按顺序给出一些树,输出在这之前和该树同构的最早出现的树
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=55;
const int maxm=105;
const int INF=1e8;
const ll llINF=1e18;
const int maxN=1e6+5;
const int maxp=1e6+5;
bool isPrime[maxN];
int Prime[maxp], primecnt = 0;
void GetPrime(int n){//筛到n
memset(isPrime, 1, sizeof(isPrime));
isPrime[1] = 0;//1不是素数
for(int i = 2; i <= n; i++){
if(isPrime[i])//没筛掉
Prime[++primecnt] = i; //i成为下一个素数
for(int j = 1; j <= primecnt && i*Prime[j] <= n; j++) {
isPrime[i*Prime[j]] = 0;
if(i % Prime[j] == 0)//i中也含有Prime[j]这个因子
break;
}
}
}
struct edge{
int v,next;
}E[maxm];
int head[maxn],tot;
void addedge(int u,int v){
E[++tot].v=v;
E[tot].next=head[u];
head[u]=tot;
}
int sz[maxn],maxnum[maxn],minn;
void dfs(int u,int fa,int n){
sz[u]=1;
int res=0;
for(int i=head[u];i;i=E[i].next){
int v=E[i].v;
if(v==fa) continue;
dfs(v,u,n);
sz[u]+=sz[v];
res=max(res,sz[v]);
}
res=max(res,n-sz[u]);
maxnum[u]=res;
minn=min(minn,maxnum[u]);
}
ll hx[maxn],sz2[maxn];
void dfsh(int u,int fa){
sz2[u]=hx[u]=1;
for(int i=head[u];i;i=E[i].next){
int v=E[i].v;
if(v==fa)continue;
dfsh(v,u);
hx[u]+=hx[v]*Prime[sz2[v]];
sz2[u]+=sz2[v];
}
}
void getHash(int u){
dfsh(u,0);
}
ll ans[maxn];
void init(int n){
minn=INF;
tot=0;
memset(head,0,sizeof(head));
}
int main () {
GetPrime(maxN-5);
int M;
scanf("%d",&M);
map<ll,int>vis;
for(int i=1;i<=M;i++){
int n;
scanf("%d",&n);
init(n);
for(int u=1;u<=n;u++){
int fa;
scanf("%d",&fa);
if(fa!=0){
addedge(u,fa);
addedge(fa,u);
}
}
dfs(1,0,n);
vector<int>rt;
for(int u=1;u<=n;u++){
if(maxnum[u]==minn){
rt.push_back(u);
}
}
for(auto u:rt){
getHash(u);
ans[i]=max(ans[i],hx[u]);
}
if(!vis.count(ans[i])){
vis[ans[i]]=i;
printf("%d
",i);
}
else{
printf("%d
",vis[ans[i]]);
}
}
}
P4323 每个点哈希
题意:给出一棵树a,大小为n,给出另一个大小为n+1的树b,b是在a树的基础上加上一个叶子节点,并打乱节点顺序,问b树中编号最小的可能是新加入点的点。
思路:对于a树的所有点,处理出以其为根的hash值,存在set中。同样预处理b树中所有点以其为根的hash值,对于b树中所有的和叶子节点相邻的点,若去掉相邻的叶子节点,以其为根的哈希值就会-2,判断-2后的哈希值是否在set中即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int maxm=2e5+5;
const int INF=1e8;
const int maxN=2e6+5;
const int maxp=2e6+5;
bool isPrime[maxN];
int Prime[maxp], primecnt = 0;
void GetPrime(int n){//筛到n
memset(isPrime, 1, sizeof(isPrime));
isPrime[1] = 0;//1不是素数
for(int i = 2; i <= n; i++){
if(isPrime[i])//没筛掉
Prime[++primecnt] = i; //i成为下一个素数
for(int j = 1; j <= primecnt && i*Prime[j] <= n; j++) {
isPrime[i*Prime[j]] = 0;
if(i % Prime[j] == 0)//i中也含有Prime[j]这个因子
break;
}
}
}
struct Tree{
struct edge{
int v,next;
}E[maxm];
int head[maxn],tot;
void addedge(int u,int v){
E[++tot].v=v;
E[tot].next=head[u];
head[u]=tot;
}
ll hx[maxn],sz2[maxn];
void dfs1(int u,int fa){
sz2[u]=hx[u]=1;
for(int i=head[u];i;i=E[i].next){
int v=E[i].v;
if(v==fa)continue;
dfs1(v,u);
hx[u]+=hx[v]*Prime[sz2[v]];
sz2[u]+=sz2[v];
}
}
ll ghx[maxn];
void dfs2(int u,int fa,ll faf,int n){//faf:fa除去u子树,剩余树的哈希值
ghx[u]=hx[u]+faf*(Prime[n-sz2[u]]);
faf*=Prime[n-sz2[u]];//此时,faf=以u为根fa方向的树的哈希值-1
for(int i=head[u];i;i=E[i].next){
int v=E[i].v;
if(v!=fa){
dfs2(v,u,faf+hx[u]-hx[v]*(Prime[sz2[v]]),n);//hx[u]-hx[v]*(Prime[size[v]):以u为根,除去fa和v方向的子树,剩余的哈希值
}
}
}
}t1,t2;
int du[maxn],near[maxn];
int main () {
GetPrime(maxN-5);
int n;
scanf("%d",&n);
for(int i=1;i<=n-1;i++){
int u,v;
scanf("%d%d",&u,&v);
t1.addedge(u,v);
t1.addedge(v,u);
}
for(int i=1;i<=n;i++){
int u,v;
scanf("%d%d",&u,&v);
du[u]++;du[v]++;
near[u]=v,near[v]=u;//near存一个相邻点,对于叶子节点相邻点只有一个
t2.addedge(u,v);
t2.addedge(v,u);
}
t1.dfs1(1,0);
t1.dfs2(1,0,0,n);
set<int>vis;
for(int i=1;i<=n;i++){
vis.insert(t1.ghx[i]);
}
t2.dfs1(1,0);
t2.dfs2(1,0,0,n+1);
for(int i=1;i<=n+1;i++){
if(du[i]!=1)continue;
ll temp=t2.ghx[near[i]]-2;
if(vis.count(temp)){
printf("%d
",i);
break;
}
}
}