树Hash学习笔记
树Hash是用来判断两棵树是否同构(即去掉编号后形态一样的方法)
子树无顺序的树同构
子树排列顺序不同,算一种树。
对于有根树,我们从根开始DFS,对每个子树维护哈希值(h_x)对于同构的树,他们根节点的(h)一定相同。
一个比较好的递推方法是:
[h_x=1+sum_{y in son(x)} h_ycdot P(sz_y)
]
其中(P(i))表示第(i)个质数,(sz_x)表示(x)的子树大小.这样可以减少冲突。
对于无根树,我们需要定一个根,且这个根的选择不会随着编号的变化而变化。那么就可以选择树的重心来DFS.如果有两个重心,就把Hash值分别取(max)或(min).
当然,也可以求出以每个点为根的DP值,类似树形DP中的换根法,复杂度(O(n))
vint root=0;
int sz[maxn+5],f[maxn+5];
void dfs1(int x,int fa){//重心不会因为编号方式改变,以它为根可减少误判
sz[x]=1;
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(y!=fa){
dfs1(y,x);
sz[x]+=sz[y];
f[x]=max(f[x],sz[y]);
}
}
f[x]=max(f[x],n-sz[x]);
if(f[x]<f[root]||root==0) root=x;
}
ll hsh[maxn+5];
void dfs2(int x,int fa){
sz[x]=1;
hsh[x]=1;
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(y!=fa){
dfs2(y,x);
hsh[x]+=hsh[y]*prime[sz[y]];
sz[x]+=sz[y];
}
}
}
换根
void dfs3(int x,int fa){//求出以每个点为根的hash值
for(int i=0;i<T[x].size();i++){
int y=T[x][i];
if(y!=fa){
hshRt[y]=(hshRt[x]-hsh[y]*prime[sz[y]])*prime[totsz-sz[y]]+hsh[y];
dfs3(y,x);
}
}
}
当然,由于树Hash的本质仍然是哈希,有时可能会被卡。此时可以对h取模(或自然溢出),或random_shuffle()
质数数组,或改变对应关系
子树有顺序的树同构
由于不能随便交换子树(比如圆方树的同构),我们可以类似字符串hash的方法,按顺序把每个子树的hash值当做字符
[h_x=(h_x cdot P+h_y)mod M
]