哈希算法在判定树同构方面的应用(上)

(一)需要掌握的前置知识:

(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)基本的树上 dfsdfsdfs 遍历整棵树。

(4)树上重心的求解:

树的重心: 找到一个点 xxx, 以点 xxx 为根时,其所有的子树中最大的子树节点数最少,那么这个点 xxx 就是这棵树的重心。(以重心为根时,最大子树最小)

性质:
(1)树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个重心,他们的距离和一样。(这里树的边权都定义为 111)
(2)把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
(3)一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
(4)一棵树最多有两个重心,且相邻。
(5)删除重心后所得的所有子树,节点数不超过原树的1/2。

其他说明:判定树同构有多种方法,且树同构的哈希也有多种方法,接下来仅介绍一种常用的哈希方法,其他的哈希方法类推即可。

(二)例题一:洛谷–P5043 【模板】树同构([BJOI2015]树的同构)

题目连接

题面截图:

题意:

给定 MMM 棵无根树,对于第 iii 棵无根树寻找 1−i1-i1−i 中与第 iii 棵无根树同构的最小的无根树的编号。

题解:

根据题意,我们很容易写出伪代码:

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∈sonxf[y]∗p[si[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] 取自然溢出,即对 2642^{64}264 取模。

其中 y∈sonxy\in son_xy∈sonx​ 表示在有根树中 yyy 是 xxx 的儿子节点。f[y]f[y]f[y] 是 yyy 的 fff 函数。

ppp 数组是我们取前 kkk (k>=si[root]k>=si[root]k>=si[root])个质数,再随机打乱后的数组。
随机打乱 ppp 数组。

random_shuffle(p+1,p+k+1);//k可以自己选定

si[x]si[x]si[x] 表示 xxx 子树的大小。

那么如果判定两个有根树同构呢,我们认为 如果f[root1]=f[root2]f[root1]=f[root2]f[root1]=f[root2],那么就可以认为两棵树同构。事实上虽然 f[root1]=f[root2]f[root1]=f[root2]f[root1]=f[root2] 有可能两棵树不一定同构,如果在确认算法正确且时间允许的情况下,我们可以多哈希来判定树的同构。事实上多哈希来判定同构,冲突的概率就极低了,数据一般也不容易构造。

现在我们来总结一下怎样判定两棵根分别为 root1,root2root1,root2root1,root2 的树的同构的方法:
(1)求解 ppp 数组,利用埃氏筛或者欧拉筛。
(2)在树上进行 dfsdfsdfs 求解 fff 数组
(3)比较 f[root1]f[root1]f[root1] 与 f[root2]f[root2]f[root2]

现在来考虑如何判定两棵无根树是否同构:
假设我们现在有两棵无根树 A,BA,BA,B 同构,那么我们在 AAA 树中选一点 xxx 作为 AAA 树的根,那么在 BBB 树中一定存在一点 yyy,使得 BBB 树以 yyy 为根与 AAA 树以 xxx 为根同构。

相反的,如果我们在 AAA 树中选一点 xxx 作为 AAA 树的根,那么在 BBB 树中如果存在一点 yyy,使得 BBB 树以 yyy 为根与 AAA 树以 xxx 为根同构,那么两棵 无根树 A,BA,BA,B 同构。

我们对 AAA 树中的某一点 xxx ,去找在 BBB 树中是否能存在一个点 yyy ,使得 BBB 树中以点 yyy 为根的树与 AAA 树中以点 xxx 为根的树同构。

那么我们能得到判定无根树同构的算法:
对于 AAA 树中的某一点 xxx,求出AAA 树中以 xxx 为根的 f[x]f[x]f[x],然后在 BBB 树中寻找,是否存在一点 yyy,使得 BBB 树中以 yyy 为根的 f[y]f[y]f[y] 等于AAA 树中以 xxx 为根的 f[x]f[x]f[x]。若存在,则说明 A,BA,BA,B同构,否则不同构。

为了方便判别,我们可以有以下处理:

对于 AAA 树的每个节点 xxx ,求出 AAA 树中以 xxx 为根的 f[x]f[x]f[x],将这些 f[x]f[x]f[x] 保存至 aaa 数组。
对于 BBB 树的每个节点 yyy ,求出 BBB 树中以 yyy 为根的 f[y]f[y]f[y],将这些 f[y]f[y]f[y] 保存至 bbb 数组。

将 a,ba,ba,b 两数组按照相同的排序规则排序 (都升序或者都降序),那么如果最终 A,BA,BA,B两棵树的节点数相同且 a,ba,ba,b 两数组对应位置相同,那么认为 A,BA,BA,B 两棵树同构。

回到本题:
我们对 MMM 棵树,对于第 iii 棵树,我们保存下以 xxx 为根的 f[x]f[x]f[x] 值,记为 ha[i][x]=f[x]ha[i][x]=f[x]ha[i][x]=f[x],然后再对 ha[i]ha[i]ha[i] 数组进行排序。

判定两棵树是否同构的时候只需要比较两棵树的大小和 ha[i],ha[j]ha[i],ha[j]ha[i],ha[j] 两个数组即可。

空间复杂度 O(n2)O(n^2)O(n2) ,时间复杂度 O(n3)O(n^3)O(n3)。(因为 nnn 和 mmm 同级别,就都当作 nnn 来计算)

因为这里节点数 nnn 比较小,所以我们直接打质数表即可。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<queue>
#define ll long long
#define pr make_pair
#define pb push_back
#define lc (cnt<<1)
#define rc (cnt<<1|1)
using namespace std;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e15;
const int mod=1e9+7;
const int maxn=55;
int head[maxn],ver[maxn<<1],nt[maxn<<1];
int n[maxn],si[maxn],tot=1;
unsigned ll ha[maxn][maxn],f[maxn];
int p[]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229};void init(void)
{memset(head,0,sizeof(head));tot=1;
}void add(int x,int y)
{ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}void dfs(int x,int fa)
{f[x]=si[x]=1;for(int i=head[x];i;i=nt[i]){int y=ver[i];if(y==fa) continue;dfs(y,x);f[x]+=f[y]*p[si[y]];si[x]+=si[y];}
}bool check(int i,int j)
{for(int k=1;k<=n[i];k++){if(ha[i][k]!=ha[j][k]) return false;}return true;
}int main(void)
{random_shuffle(p+1,p+50+1);int m,fa;scanf("%d",&m);for(int i=1;i<=m;i++){init();scanf("%d",&n[i]);for(int j=1;j<=n[i];j++){scanf("%d",&fa);if(fa) add(fa,j),add(j,fa);}for(int j=1;j<=n[i];j++){dfs(j,0);ha[i][j]=f[j];}sort(ha[i]+1,ha[i]+n[i]+1);}for(int i=1;i<=m;i++){for(int j=1;j<=i;j++){if(n[i]==n[j]&&check(i,j)){printf("%d\n",j);break;}}}return 0;
}

虽然 O(n3)O(n^3)O(n3) 的算法足够通过本题,但是如果 n=5e3n=5e3n=5e3 级别的就不能再处理了,考虑优化。

思考:我们为什么要求第 iii 棵无根树分别以每个节点 xxx 为根的 f[x]f[x]f[x] ?

因为 两棵无根树A,BA,BA,B 同构 等价于 对于AAA 中以某一点 xxx为根的有根树, BBB 中存在以 yyy 为根的有根树与 AAA 中以 xxx 为根的有根树同构。我们需要在 BBB 中找到 yyy 。

那么我们能不能快速在A,BA,BA,B中分别找到一点 x,yx,yx,y,使得 x,yx,yx,y 是相对应的,即 让 A,BA,BA,B 是否同构等价于以xxx为根的有根树与以yyy为根的有根树是否同构。

能不能找到这样的两点 x,yx,yx,y 呢? 重心!重心!重心!

于是判定同构的条件转化为:
两棵无根树 A,BA,BA,B 是否同构,等价于他们以重心为根的有根树是否同构。

考虑一下为什么?若重心唯一,那么两棵无根树 A,BA,BA,B 同构时的重心必定对应;若重心都不唯一,那么两棵无根树 A,BA,BA,B 同构时两棵树的重心也一一对应。

即,若两棵树同构,那么重心为根的树一定是同构的。如果重心为根的树是同构的,那么两棵树一定是同构的。

那么求解无根树是否同构的方法可以优化为以下做法:
(1)求解树 A,BA,BA,B 的重心,重心至多有两个。
(2)求解树 A,BA,BA,B 以重心为根的 fff。
(3)判断是否同构。

回到本题:
我们只需要求解第 iii 棵树的重心,然后求解重心的 fff 即可。

空间复杂度 O(n)O(n)O(n),时间复杂度 O(n2)O(n^2)O(n2)

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<unordered_map>
#include<unordered_set>
#include<set>
#include<ctime>
#define ui unsigned int
#define ll long long
#define llu unsigned ll
#define ld long double
#define pr make_pair
#define pb push_back
#define lc (cnt<<1)
#define rc (cnt<<1|1)
#define len(x)  (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
#define fhead(x) for(int i=head[(x)];i;i=nt[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
using namespace std;const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const double alpha=0.75;
const int mod=1e9+7;
const double eps=1e-8;
const double pi=acos(-1.0);
const int hp=13331;
const int maxn=55;
const int maxm=100100;
const int maxp=100100;
const int up=1100;
int head[maxn],ver[maxn<<1],nt[maxn<<1];
int n[maxn],si[maxn],dp[maxn],tot=1,rt1,rt2,nown;
llu f[maxn],rt1f[maxn],rt2f[maxn];
int p[]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229};void init(void)
{memset(head,0,sizeof(head));tot=1;rt1=0,rt2=0;
}void add(int x,int y)
{ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}void dfs1(int x,int fa)
{si[x]=1,dp[x]=0;for(int i=head[x];i;i=nt[i]){int y=ver[i];if(y==fa) continue;dfs1(y,x);si[x]+=si[y];dp[x]=max(dp[x],si[y]);}dp[x]=max(dp[x],nown-si[x]);if(rt1==0||dp[x]<dp[rt1]) rt1=x,rt2=0;else if(dp[x]==dp[rt1]) rt2=x;
}void dfs2(int x,int fa)
{f[x]=si[x]=1;for(int i=head[x];i;i=nt[i]){int y=ver[i];if(y==fa) continue;dfs2(y,x);f[x]+=f[y]*p[si[y]];si[x]+=si[y];}
}bool check(int i,int j)
{return (rt1f[i]==rt1f[j]&&rt2f[i]==rt2f[j])||(rt1f[i]==rt2f[j]&&rt2f[i]==rt1f[j]);
}int main(void)
{random_shuffle(p+1,p+50+1);int m,fa;scanf("%d",&m);for(int i=1;i<=m;i++){init();scanf("%d",&n[i]);for(int j=1;j<=n[i];j++){scanf("%d",&fa);if(fa) add(fa,j),add(j,fa);}nown=n[i];dfs1(1,0);if(rt1!=0) dfs2(rt1,0),rt1f[i]=f[rt1];if(rt2!=0) dfs2(rt2,0),rt2f[i]=f[rt2];}for(int i=1;i<=m;i++){for(int j=1;j<=i;j++){if(check(i,j)){printf("%d\n",j);break;}}}return 0;
}

哈希算法在判定树同构方面的应用(上)相关推荐

  1. 哈希算法在判定树同构方面的应用(下)

    哈希算法在判定树同构方面的应用 在上一篇文章中我们介绍了 枚举根节点哈希 和 求重心哈希 两种方法来判断两棵无根树是否同构. 但是如果有些题目中我必须要计算出每个根节点的 fff 值,且 n≤1e5n ...

  2. 数据结构与算法 -判定树和哈夫曼树

    1. 分类与判定树 判定树是用于描述分类过程的二叉 树,每个非终端结点包含一个条件,对应一次比较:每个终端结点 包含一个种类标记, 对应于一种分类结果. 设有n个学生,现要根据他们的成绩将其划分为5类 ...

  3. 最优判定树C/C++实现(二叉链表实现)

    1.最优判定树定义: 百度和维基上也没找到定义,可能是这本书独有的,字太多了,我就直接拍图片了. 给定这张表: 如果利用huffman算法(每次找最小的两个结点去构造一个新结点)去生成一颗判定树的话, ...

  4. 一致性哈希算法的理解

    关于一致性哈希算法,网上有很多博文都有讲解.推荐2个. http://blog.codinglabs.org/articles/consistent-hashing.html http://blog. ...

  5. 常见哈希算法以及Hmac算法,BouncyCastle总结

    常见哈希算法总结 (1)什么是哈希算法? 哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输出数据进行计算,得到一个固定长度的输出摘要.哈希算法的目的;为了验证原始数据是否被篡 ...

  6. 【树的哈希/树同构】2019-2020 ICPC, Asia Jakarta Regional Contest - F. Regular Forestation

    题目链接https://codeforces.com/contest/1252/problem/F 题意 给出一棵树,问删去一个度大于1的节点,使得剩下的树两两同构.问剩下的树最多是多少. 题解 容易 ...

  7. 洛谷 - P5043 【模板】树同构([BJOI2015]树的同构)(树上哈希)

    题目链接:点击查看 题目大意:给出 m 棵树,对于第 i 棵树而言,找到 1 ~ i 中与当前树同构的最小 id 题目分析:判断有向树同构,可以预处理出质数数组 p ,然后树形 dp ,设 u 为当前 ...

  8. 数据结构 | 折半查找 /二分查找 算法细节、二分查找判定树

    一.基本思想 假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功:否则利用中间位置记录将表分成前.后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步 ...

  9. 【数据结构和算法笔记】线性表的查找(平均查找长度,二分法,判定树)

    查找: 给定一个值k,在含有n个元素的表中找出关键字等于k的元素,若找到,则查找成功,否则,查找失败 查找前首先确定(1)存放数据的数据结构是什么(2)元素是否有序 动态查找表:查找的同时做修改操作( ...

最新文章

  1. 数据库访问类(使用存储过程的)
  2. 深入探究函数返回局部变量
  3. 如何给SAP Cloud Platform的CloudFoundry环境里的subaccount添加quota
  4. 分析股票大数据_Python大数据分析量学祖师爷网站数据
  5. C#比较时间方法(3种)
  6. java test使用手册,啄木鸟(woodpecker)自动化测试工具使用手册
  7. 网络***检查分析---破解安全隐患问题回答
  8. 自动驾驶 5-3 前馈速度控制 Feedforward Speed Control
  9. 全球与中国高精度漆包扁铜线市场深度研究分析报告
  10. 推荐一款基于bootstrap的漂亮的前端模板—inspinia_admin
  11. 数学建模学习笔记(10):因子分析法
  12. Arcgis去除矢量文件Z值和/或M值方法
  13. 苹果手表连接是什么原理_为什么苹果手表是最具潜力的苹果产品
  14. 用STAR法则写简历
  15. 如何使用SIGFOX技术连接物联网?
  16. php保存word没背景图,为什么word文档明明保存了却不见了
  17. 斑马电商云品牌发布会就是一群有梦想的人在搞事情
  18. 纵观客户服务渠道变化,引领在线客服系统新方向
  19. 如何在win10新建菜单添加新的文件类型
  20. gis与计算机科学之间的联系,GIS与地图学及电子地图

热门文章

  1. 【热门主题:鬼泣5游戏xp主题】
  2. Excel批量删除插入的对象的2种方法
  3. 为什么release版本的activex 注册失败?而debug版本的却能注册成功?
  4. 操作系统实验五、进程互斥实验——理发店问题
  5. [译] JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧...
  6. python excel计算_python计算excel平均值和标准差
  7. python关键词分类_python多分类关键词搜索
  8. 虹膜识别的原理和七大应用领域
  9. 不懂运营思维模型,谈何升职加薪?思维导图带你职场闯关
  10. Android Studio中R文件错误的解决办法