首页 > 其他分享 >「笔记」树同构

「笔记」树同构

时间:2024-04-17 10:57:17浏览次数:24  
标签:std 同构 int 笔记 fa 哈希 根树

目录

写在前面

vp 的时候用到了于是来学一下。

好水。

抱歉了 AHU,但是树哈希它实在是太好写了。

树同构定义

有根树同构

对于两棵有根树 \(T_1(V_1,E_1,r_1)\) 和 \(T_2(V_2,E_2,r_2)\),如果存在一个双射 \(\varphi: V_1 \rightarrow V_2\),使得

\[\forall u,v \in V_1,(u,v) \in E_1 \iff (\varphi(u),\varphi(v)) \in E_2 \]

\(\varphi(r_1)=r_2\) 成立,那么称有根树 \(T_1(V_1,E_1,r_1)\) 和 \(T_2(V_2,E_2,r_2)\) 同构。

无根树同构

对于两棵无根树 \(T_1(V_1,E_1)\) 和 \(T_2(V_2,E_2)\),如果存在一个双射 \(\varphi: V_1 \rightarrow V_2\),使得

\[\forall u,v \in V_1,(u,v) \in E_1 \iff (\varphi(u),\varphi(v)) \in E_2 \]

成立,那么称无根树 \(T_1(V_1,E_1)\) 和 \(T_2(V_2,E_2)\) 同构。

简单的说就是,如果能够通过把树 \(T_1\) 的所有节点重新标号,使得树 \(T_1\) 和树 \(T_2\) 完全相同,那么称这两棵树同构。

树哈希

有根树

一种常用的好写的构造方法。

记 \(h_u\) 表示以 \(u\) 为根的子树的哈希值,记 \(f\) 为对多重集的哈希函数。

则可定义:

\[h_u = f(\{ h_v | v\in \operatorname{son}(u) \}) \]

实现时常采用如下的哈希函数:

\[f(S) = \left( c + \sum_{x\in S} g(x) \right)\bmod m \]

其中:

  • \(c\) 为一常数,一般取 1 即可。
  • \(m\) 为模数。
  • \(g\) 为整数到整数的映射,代码中使用了 xor shift 算法。

无根树

发现上述哈希值的定义方式天然支持换根操作。

于是可以考虑换根 DP 求得以所有节点为根的树的哈希值,再进行多重集哈希即得无根树的哈希值。

不过对于两棵无根树,更进一步地可观察得到一些性质:

  • 它们的重心数量一定相等。
  • 以重心为根的有根树一一对应。

于是可以考虑先求重心,构造出以重心为根的有根树哈希后比较哈希值,正确性有所上升。

AHU 算法

咕咕~

例题

UOJ #763. 树哈希

给定一棵以点 \(1\) 为根的树,你需要输出这棵树中最多能选出多少个互不同构的子树。
两棵有根树 \(T_1\)、\(T_2\) 同构当且仅当他们的大小相等,且存在一个顶点排列 \(\sigma\) 使得在 \(T_1\) 中 \(i\) 是 \(j\) 的祖先当且仅当在 \(T_2\) 中 \(\sigma(i)\) 是 \(\sigma(j)\) 的祖先。
\(1\le n\le 10^6\)。
5S,512MB。

有根树同构板题。

偷懒写了自然溢出。

为了不被叉掉,代码中使用了基于 C++11 特性的 std::chrono::steady_clock::now().time_since_epoch().count() 生成随机数。

//树哈希
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
const int kN = 1e6 + 10;
const int kM = kN << 1;
//=============================================================
int n, edgenum, head[kN], v[kM], ne[kM];
ULL f[kN], mask = std::chrono::steady_clock::now().time_since_epoch().count();
std::set<ULL> subtree;
//=============================================================
void Add(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
ULL trans(ULL x_) {
  x_ ^= mask;
  x_ ^= x_ << 13ll;
  x_  ^= x_ >> 7ll;
  x_ ^= mask;
  return x_;
}
void Dfs(int u_, int fa_) {
  f[u_] = 1;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    Dfs(v_, u_);
    f[u_] += trans(f[v_]);
  }
  subtree.insert(f[u_]);
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n;
  for (int i = 1; i < n; ++ i) {
    int u_, v_; std::cin >> u_ >> v_;
    Add(u_, v_), Add(v_, u_);
  }
  Dfs(1, 0);
  std::cout << subtree.size();
  return 0;
}

SP7826 - TREEISO - Tree Isomorphism

\(T\) 组数据,每组数据给定两棵大小为 \(n\) 的无根树,判断它们是否同构。
\(1\le T\le 400\),\(1\le \sum n\le 10^5\)。
261ms,1.46GB。

SPOJ 一如既往的诡异时空限制。

无根树同构板题。

考虑换根 DP 求得以所有节点为根的树的哈希值,再进行多重集哈希即得无根树的哈希值。

//树哈希
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
const int kN = 2e5 + 10;
const int kM = kN << 1;
//=============================================================
int n, edgenum, head[kN], v[kM], ne[kM];
ULL f[kN], g[kN], mask = std::chrono::steady_clock::now().time_since_epoch().count();
//=============================================================
void Add(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
void Init() {
  std::cin >> n;
  edgenum = 0;
  for (int i = 1; i <= 2 * n; ++ i) {
    head[i] = f[i] = g[i] = 0;
  }
}
ULL trans(ULL x_) {
  x_ ^= mask;
  x_ ^= x_ << 13ll;
  x_ ^= x_ >> 7ll;
  x_ ^= mask;
  return x_;
}
void Dfs1(int u_, int fa_) {
  f[u_] = 1;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    Dfs1(v_, u_);
    f[u_] += trans(f[v_]);
  }
}
void Dfs2(int u_, int fa_) {
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    g[v_] = f[v_] + trans(g[u_] - trans(f[v_]));
    // LL newf = f_ + f[u_] - trans(f[v_]);
    Dfs2(v_, u_);
  }
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    Init();
    for (int i = 1; i < n; ++ i) {
      int u_, v_; std::cin >> u_ >> v_;
      Add(u_, v_), Add(v_, u_);
    }
    for (int i = 1; i < n; ++ i) {
      int u_, v_; std::cin >> u_ >> v_;
      Add(u_ + n, v_ + n), Add(v_ + n, u_ + n);
    }
    Dfs1(1, 0), Dfs1(n + 1, 0);
    g[1] = f[1], g[n + 1] = f[n + 1];
    Dfs2(1, 0), Dfs2(n + 1, 0);

    ULL sum[2] = {0};
    for (int i = 1; i <= n; ++ i) {
      sum[0] += trans(g[i]);
      sum[1] += trans(g[i + n]);
    }
    std::cout << (sum[0] == sum[1] ? "YES\n" : "NO\n");
  }
  return 0;
}

P5043 【模板】树同构([BJOI2015]树的同构)

同样无根树同构板题。

//树哈希
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
const int kN = 2e5 + 10;
const int kM = kN << 1;
//=============================================================
int m, n, edgenum, head[kN], v[kM], ne[kM];
ULL f[kN], g[kN], mask = std::chrono::steady_clock::now().time_since_epoch().count();
std::map<ULL, int> tree;
//=============================================================
void Add(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
void Init() {
  std::cin >> n;
  edgenum = 0;
  for (int i = 1; i <= n; ++ i) {
    head[i] = f[i] = g[i] = 0;
  }
}
ULL trans(ULL x_) {
  x_ ^= mask;
  x_ ^= x_ << 13ll;
  x_ ^= x_ >> 7ll;
  x_ ^= mask;
  return x_;
}
void Dfs1(int u_, int fa_) {
  f[u_] = 1;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    Dfs1(v_, u_);
    f[u_] += trans(f[v_]);
  }
}
void Dfs2(int u_, int fa_) {
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    g[v_] = f[v_] + trans(g[u_] - trans(f[v_]));
    Dfs2(v_, u_);
  }
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> m;
  for (int id = 1; id <= m; ++ id) {
    Init();
    for (int i = 1; i <= n; ++ i) {
      int fa; std::cin >> fa;
      if (fa == 0) continue;
      Add(fa, i), Add(i, fa);
    }
    Dfs1(1, 0), g[1] = f[1], Dfs2(1, 0);
    
    ULL sum = 0;
    for (int i = 1; i <= n; ++ i) sum += trans(g[i]);
    if (!tree.count(sum)) tree[sum] = id;
    std::cout << tree[sum] << "\n";
  }
  return 0;
}

写在最后

参考:

标签:std,同构,int,笔记,fa,哈希,根树
From: https://www.cnblogs.com/luckyblock/p/18140075

相关文章

  • ROS2笔记1--简介及开发环境搭建
    一、ROS2简介1.1、ROS2概述ROS2是第二代的RobotOperatingSystem,ROS1的升级版本,解决了ROS1存在的一些问题。与ROS1相比,Linux版本与ROS2版本的选择也有关系,对应关系如下:ROS2版本Ubuntu版本FoxyUbuntu20.04GalacticUbuntu20.04HumbleUbuntu......
  • 后缀数组学习笔记
    定义后缀从字符串某个位置i到字符串末尾的子串,定义s的第i个字符为第一个元素的后缀为suf(i)。后缀数组把s的每一个后缀按照字典序排序,后缀数组sa[i]表示排名为i的后缀的起始位置的下标。rk[i]数组代表起始位置为i的后缀的排名。rk[]和sa[]是一一对应关系,互为逆运算,可以相互......
  • 《线性代数的本质》笔记(09)
    09-基变换基向量不同,则相同坐标的向量实际上并不是同一个。将新的基向量看作是线性变换,则其列应该是原本的基向量现在的位置。将一个新坐标系下的向量a(x,y)转换到我们的坐标系中:用这个矩阵乘以这个向量。原因:用两组基向量分别表示向量在两个坐标系下的位置,则结果应该是相同的。所......
  • day14_我的Java学习笔记 (常用API、Lambda、常见算法)
    1.常用API1.1Date类【案例】:计算出当前时间往后走1小时121秒之后的时间是多少。1.2SimpleDateFormat【练习】:秒杀活动1.3Calendar2.JDK8新增日期类2.1概述、LocalTime/LocalDate/LocalDateTime2.2Ins......
  • day12_我的Java学习笔记 (package包、权限修饰符_private+缺省+protected+public、fin
    1.包IDEA配置自动导包:2.权限修饰符同一个类中的,【private、缺省、protected、public】都可以访问同一个包中的其他类,【private】不可以访问,【缺省、protected、public】都可以访问不同包下的无关类,【private、缺省、protected】都不可以访问,只有【public......
  • 笔记本电脑上的聊天机器人: 在英特尔 Meteor Lake 上运行 Phi-2
    对应于其强大的能力,大语言模型(LLM)需要强大的算力支撑,而个人计算机上很难满足这一需求。因此,我们别无选择,只能将它们部署至由本地或云端托管的性能强大的定制AI服务器上。为何需要将LLM推理本地化如果我们可以在典配个人计算机上运行最先进的开源LLM会如何?好处简直太......
  • 模型微调-书生浦语大模型实战营学习笔记&大语言模型5
    大语言模型-5.模型微调书生浦语大模型实战营学习笔记-4.模型微调本节对应的视频教程为B站链接。笔记对视频的理论部分进行了整理。大模型的训练过程模型视角这里原视频用的“分类”这个名字,我看到的时候还有点懵......
  • Java 常用笔记
    问题1org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'jobConfParser'definedinclasspathresource[com/cxytiandi/elasticjob/autoconfigure/JobParserAutoConfiguration.class]:Initializationofbeanfailed;n......
  • 笔记:OpenCV3和Qt5 计算机视觉应用开发(一)
    目标:学习《OpenCV3和Qt5计算机视觉应用开发》,记录总结学习过程。第一章OpenCV和Qt简介开发环境系统版本:Ubuntu16.04.7LTSQt版本:Qt5.9.5OpenCV版本:opencv-3.3.0虚拟机版本:VMware®Workstation16Pro(16.2.2build-19200509)学习总结1,安装Linux开发环境终端运行:sudoapt-get......
  • 笔记:OpenCV3和Qt5 计算机视觉应用开发(二)
    目标:学习《OpenCV3和Qt5计算机视觉应用开发》,记录总结学习过程。第2章创建第一个Qt+OpenCV项目学习总结1,信号与槽机制。2,Qt对象树机制实现自动内存管理。3,问题:程序异常结束。OpenCVError:Unspecifiederror(couldnotfindawriterforthespecifiedextension)inimwrite......