树的同构

  • problem
  • solution
  • code
  • solution
  • code

problem

模板题

solution

Ⅰ. 最小表示法

将树转化为 0/10/10/1 括号序列:从根开始 dfs\text{dfs}dfs,000 就往下遍历一个儿子,111 就返回,构成一个 2×n2\times n2×n 的括号序列。

显然,括号序列与树的形态是唯一对应的。

  • 有根树

    • 若儿子遍历顺序是固定的。显然括号序列只有唯一一种。
    • 若儿子遍历顺序不固定。就会有多种合法的括号序列,不妨钦定字典序最小的为这棵树的括号序列,这个特殊的括号序列有单独的名称:最小表示

显然,两棵有根树的最小表示相同是同构的充要条件

具体实现:递归地构造,对于点 uuu,先把儿子 vvv 的括号序列都处理出来后,按照儿子的字典序从小到大排序,然后顺次接起来。在这个拼接的括号序列外面用 000(进入 uuu 子树求解)111(完成 uuu 子树内的遍历) “包起来”就表示把 uuu 子树遍历完然后返回上一层的最小表示了。

时间复杂度为 O(n2)O(n^2)O(n2)。最坏情况为链。

因为括号序列肯定是用字符串 string\text{string}string 类型储存。

那么 f[u]+=f[v]f[u]+=f[v]f[u]+=f[v] 这一句话的操作复杂度其实是 O(len)O(len)O(len) 的。

  • 无根树

比较暴力的想法就是对于每个点都当作根,做一遍 dfs\text{dfs}dfs。时间复杂度为 O(n3)O(n^3)O(n3)。

对于本题而言,又有 mmm 棵树,时间复杂度就是 O(n3m)O(n^3m)O(n3m) 的。

实际上,最小表示法就是为了定义一个规则,让一棵树拥有唯一的括号序列。如果喜欢最大表示法也是可以的。

所以可以对无根树找一个特殊的规则,来区别不同构的树。

这里我们选择重心为根时的括号序列当作无根树的括号序列代表。

两个重心的话就取字典序较小的。

时间复杂度就降为 O(n2m)O(n^2m)O(n2m)。

code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 55
int n, m, Max, cnt;
string Min;
struct node { int to, nxt; }E[maxn << 1];
int head[maxn], siz[maxn], MaxSiz[maxn];
string f[maxn], g[maxn], mp[maxn];void init() {memset( head, -1, sizeof( head ) );memset( MaxSiz, 0, sizeof( MaxSiz ) );cnt = 0; Max = n; Min = "1";
}void addedge( int u, int v ) {E[cnt] = { v, head[u] };head[u] = cnt ++;
}void dfs1( int u, int fa ) {siz[u] = 1;for( int i = head[u];~ i;i = E[i].nxt ) {int v = E[i].to;if( v == fa ) continue;dfs1( v, u );siz[u] += siz[v];MaxSiz[u] = max( MaxSiz[u], siz[v] );}MaxSiz[u] = max( MaxSiz[u], n - siz[u] );Max = min( Max, MaxSiz[u] );
}void dfs2( int u, int fa ) {f[u] = "0";for( int i = head[u];~ i;i = E[i].nxt )if( E[i].to ^ fa ) dfs2( E[i].to, u );int tot = 0;for( int i = head[u];~ i;i = E[i].nxt )if( E[i].to ^ fa ) g[++ tot] = f[E[i].to];sort( g + 1, g + tot + 1 );for( int i = 1;i <= tot;i ++ ) f[u] += g[i];f[u] += "1";
}int main() {scanf( "%d", &m );for( int j = 1;j <= m;j ++ ) {scanf( "%d", &n );init();for( int i = 1, x;i <= n;i ++ ) {scanf( "%d", &x );if( x ) addedge( x, i ), addedge( i, x );}dfs1( 1, 0 );for( int i = 1;i <= n;i ++ )if( MaxSiz[i] == Max ) {dfs2( i, 0 );Min = min( Min, f[i] );}mp[j] = Min;for( int i = 1;i <= j;i ++ )if( mp[i] == mp[j] ) {printf( "%d\n", i );break;}}return 0;
}

solution

Ⅱ . 树哈希

多项式哈希,即 [1,r][1,r][1,r] 的哈希值减去 r−l+1r-l+1r−l+1 乘上 [1,l][1,l][1,l] 的哈希值。

同理,有根树且儿子有顺序,哈希就是一一对应,正确的。

如果有根树且儿子没顺序,就按照儿子的哈希值排序后,再哈希。

有根树扩展到无根树也是寻找重心。

得出哈希值后暴力比较即可。

时间复杂度为 O(nmlog⁡n)O(nm\log n)O(nmlogn),瓶颈在于排序。(如果你不喜欢 log⁡\loglog,基排就行)

其实树哈希就是没有最小表示法的字符串相关操作带来的巨大复杂度而已。

哈希唯一的缺点就是会冲突,不能保证一定稳定,但也不至于很容易就被卡掉。

code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
#define mod 1019260817
#define base 19491001
#define maxn 55
struct node { int to, nxt; }E[maxn << 1];
pair < int, int > f[maxn], g[maxn];
int Pow[maxn], Hash[maxn], head[maxn], siz[maxn], MaxSiz[maxn], dep[maxn];
int n, m, cnt, rt1, rt2, Max;void addedge( int u, int v ) {E[cnt] = { v, head[u] };head[u] = cnt ++;
}void dfs1( int u, int fa ) {siz[u] = 1;for( int i = head[u];~ i;i = E[i].nxt ) {int v = E[i].to;if( v == fa ) continue;dfs1( v, u );siz[u] += siz[v];MaxSiz[u] = max( MaxSiz[u], siz[v] );}MaxSiz[u] = max( MaxSiz[u], n - siz[u] );if( MaxSiz[u] < Max ) Max = MaxSiz[u], rt1 = u, rt2 = 0;else if( MaxSiz[u] == Max ) rt2 = u;
}void dfs2( int u, int fa ) {Hash[u] = Pow[siz[u] = 1] * dep[u] % mod;for( int i = head[u];~ i;i = E[i].nxt ) if( E[i].to ^ fa ) dep[E[i].to] = dep[u] + 1, dfs2( E[i].to, u );int tot = 0;for( int i = head[u];~ i;i = E[i].nxt )if( E[i].to ^ fa ) g[++ tot] = { Hash[E[i].to], siz[E[i].to] };sort( g + 1, g + tot + 1 );for( int i = 1;i <= tot;i ++ )Hash[u] = ( Hash[u] + g[i].first * Pow[siz[u]] ) % mod, siz[u] += g[i].second;
}signed main() {Pow[0] = 1;for( int i = 1;i < maxn;i ++ ) Pow[i] = Pow[i - 1] * base % mod;scanf( "%lld", &m );for( int k = 1;k <= m;k ++ ) {memset( MaxSiz, 0, sizeof( MaxSiz ) );memset( head, -1, sizeof( head ) );cnt = 0; Max = mod;scanf( "%lld", &n );for( int i = 1, x;i <= n;i ++ ) {scanf( "%lld", &x );if( x ) addedge( i, x ), addedge( x, i );}dfs1( 1, 0 );dep[rt1] = 1;dfs2( rt1, 0 );f[k].first = Hash[rt1];if( ! rt2 ) goto pass;dep[rt2] = 1;dfs2( rt2, 0 );f[k].second = Hash[rt2];if( f[k].first > f[k].second ) swap( f[k].first, f[k].second );pass : ; }for( int i = 1;i <= m;i ++ )for( int j = 1;j <= i;j ++ )if( f[i] == f[j] ) { printf( "%lld\n", j ); break; }return 0;
}

树的同构模板题(法1.最小表示法+法2.树哈希)相关推荐

  1. 洛谷P3384 - 树链剖分(树链剖分模板题)

    题目链接 https://www.luogu.org/problemnew/show/P3384 [描述] 树链剖分模板题,记一下板子 #include<bits/stdc++.h> #d ...

  2. HDU1166 敌兵布阵(树状数组模板题)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  3. hdu 1166 敌兵布阵 树状数组 模板题

    这题是树状数组入门的一模板题,非常基础,被小白成为"赤裸裸"的入门题,哈哈,一个plus,一个sum全部搞定 #include<stdio.h> #include< ...

  4. 洛谷3384(树链剖分模板题)

    题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式 ...

  5. BZOJ 3223: Tyvj 1729 文艺平衡树-Splay树(区间翻转)模板题

    3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 6881  Solved: 4213 [Submit][S ...

  6. 一维OTSU法、最小交叉熵法、二维OTSU法及C++源码

    1.最大类间方差法(Otsu法) 该算法是日本人Otsu提出的一种动态阈值分割算法.它的主要思想是按照灰度特性将图像划分为背景和目标2部分,划分依据为选取门限值,使得背景和目标之间的方差最大.(背景和 ...

  7. C++树状数组模板题 敌兵布阵解题报告

    题目描述: C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活 ...

  8. 【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构

    本次笔记内容: 3.3.1 先序中序后序遍历 3.3.2 中序非递归遍历 3.3.3 层序遍历 3.3.4 遍历应用例子 小白专场:题意理解及二叉树表示 小白专场:程序框架.建树及同构判别 文章目录 ...

  9. 03-树1 树的同构 判断树是否同构的略简单方法

    如何判断树的同构? 分情况讨论: 1.两颗树都为空,同构. 2.两棵树有一颗为空,不同构. 3.两棵树都不为空. (a) 根结点数据不相同 (b) 根结点数据相同.要是r1的左子树与r2的左子树同构& ...

最新文章

  1. Java SE 9(JDK9)环境安装及交互式编程环境Jshell使用示例
  2. linux 定时器中断 imx,NXP iMX8 存储性能测试
  3. 下一个排列Python解法
  4. CentOS 6系统启动流程细微解读,不可忽略引导扇区的1.5阶段。
  5. pysimplegui 显示 html,PySimpleGUI 的第一个桌面软件
  6. 可复用的基于ARM的W5100底层驱动设计
  7. 随书光盘资源下载/提取码(二)
  8. Flink 动态配置(参数 算子 CEP)
  9. Smobiler实现手机弹窗
  10. 关于ORACLE删除分区
  11. android 绘制分割线,Android 布局中分割线创建的三种方式
  12. 网页微博连接不上服务器,新浪微博网络出错加载不了怎么办
  13. 计算机视觉论文速递(三)YOLO-Pose:《Enhancing YOLO for Multi Person Pose .....》实时性高且易部署的姿态估计模型
  14. String Utils 工具类介绍大全 isAnyEmpty isAnyBlank stripToNull 等,一篇文章找全所有答案!
  15. vue中的then方法
  16. 解决IDEA中Tomcat服务器端口号被占问题
  17. 为win7原版镜像整合usb3驱动
  18. git中手动删除的文件如何在git中删除
  19. 2021年茶艺师(中级)考试题及茶艺师(中级)模拟考试
  20. 深入理解Java虚拟机 笔记

热门文章

  1. 收藏 : 50个Excel逆天功能,一秒变“表哥”
  2. java sql objects_Java SQL注入学习笔记
  3. dubbo优势_Dubbo 迈出云原生重要一步 应用级服务发现解析
  4. php 电压 异常,tv断线警告是什么原因
  5. pixelbook安装linux系统,谷歌Pixelbook可以运行Fuchsia操作系统 正测试
  6. 程序显示文本框_【教程】TestComplete测试桌面应用程序教程(二)
  7. java socket 多次write_java NIO2异步socket的write事件与read事件的完成次数不一致是怎么回事...
  8. sql倒序排列取第一条_从零学会SQL·三——汇总分析
  9. mockito mock void方法_使用 Junit + Mockito 实践单元测试!
  10. html 页面工具,html页面工具-htmlUnit