哈希算法在判定树同构方面的应用(上)
(一)需要掌握的前置知识:
(1)素数筛法:埃氏筛或者欧拉筛均可以。
以下为欧拉筛:
const int maxn=100100;
int p[maxn],cnt=0;
bool ha[maxn];
void Prime(void)
{
ha[1]=true;
for(int i=2;i<maxn;i++)
{
if(!ha[i]) p[++cnt]=i;
for(int j=1;j<=cnt&&i*p[j]<maxn;j++)
{
ha[i*p[j]]=true;
if(i%p[j]==0) break;
}
}
}
(2)一般的哈希知识。
(3)基本的树上 d f s dfs dfs 遍历整棵树。
(4)树上重心的求解:
树的重心: 找到一个点 x x x, 以点 x x x 为根时,其所有的子树中最大的子树节点数最少,那么这个点 x x x 就是这棵树的重心。(以重心为根时,最大子树最小)
性质:
(1)树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个重心,他们的距离和一样。(这里树的边权都定义为 1 1 1)
(2)把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
(3)一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
(4)一棵树最多有两个重心,且相邻。
(5)删除重心后所得的所有子树,节点数不超过原树的1/2。
其他说明:判定树同构有多种方法,且树同构的哈希也有多种方法,接下来仅介绍一种常用的哈希方法,其他的哈希方法类推即可。
(二)例题一:洛谷–P5043 【模板】树同构([BJOI2015]树的同构)
题面截图:
题意:
给定 M M M 棵无根树,对于第 i i i 棵无根树寻找 1 − i 1-i 1−i 中与第 i i i 棵无根树同构的最小的无根树的编号。
题解:
根据题意,我们很容易写出伪代码:
for(int i=1;i<=m;i++)
{
for(int j=1;j<=i;j++)
{
if(第 i 棵树与第 j 棵树同构) //找到与第 i 棵树同构的树
{
printf("%d\n",j);
break;
}
}
}
现在问题的关键转化为判断两棵无根树是否同构。
先来考虑问题的简化版:两个有根树如何判定是否同构。
类似于字符串的哈希,我们给树的每个节点附一个权值,记为 f [ x ] f[x] f[x]
我们设 f [ x ] = 1 + ∑ y ∈ s o n x f [ y ] ∗ p [ s i [ y ] ] f[x]=1+\sum\limits_{y\in son_x}f[y]*p[si[y]] f[x]=1+y∈sonx∑f[y]∗p[si[y]]。这里 f [ x ] f[x] f[x] 取自然溢出,即对 2 64 2^{64} 264 取模。
其中 y ∈ s o n x y\in son_x y∈sonx 表示在有根树中 y y y 是 x x x 的儿子节点。 f [ y ] f[y] f[y] 是 y y y 的 f f f 函数。
p p p 数组是我们取前 k k k ( k > = s i [ r o o t ] k>=si[root] k>=si[root])个质数,再随机打乱后的数组。
随机打乱 p p p 数组。
random_shuffle(p+1,p+k+1);//k可以自己选定
s i [ x ] si[x] si[x] 表示 x x x 子树的大小。
那么如果判定两个有根树同构呢,我们认为 如果 f [ r o o t 1 ] = f [ r o o t 2 ] f[root1]=f[root2] f[root1]=f[root2],那么就可以认为两棵树同构。事实上虽然 f [ r o o t 1 ] = f [ r o o t 2 ] f[root1]=f[root2] f[