树Hash学习笔记

树Hash学习笔记

树Hash是用来判断两棵树是否同构(即去掉编号后形态一样的方法)

子树无顺序的树同构

子树排列顺序不同,算一种树。

对于有根树,我们从根开始DFS,对每个子树维护哈希值\(h_x\)对于同构的树,他们根节点的\(h\)一定相同。
一个比较好的递推方法是:

\[h_x=1+\sum_{y \in son(x)} h_y\cdot 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)\bmod M \]

posted @ 2021-01-14 16:58  birchtree  阅读(110)  评论(0编辑  收藏  举报