「笔记」树同构
写在前面
vp 的时候用到了于是来学一下。
好水。
抱歉了 AHU,但是树哈希它实在是太好写了。
树同构定义
有根树同构
对于两棵有根树 和 ,如果存在一个双射 ,使得
且 成立,那么称有根树 和 同构。
无根树同构
对于两棵无根树 和 ,如果存在一个双射 ,使得
成立,那么称无根树 和 同构。
简单的说就是,如果能够通过把树 的所有节点重新标号,使得树 和树 完全相同,那么称这两棵树同构。
树哈希
有根树
一种常用的好写的构造方法。
记 表示以 为根的子树的哈希值,记 为对多重集的哈希函数。
则可定义:
实现时常采用如下的哈希函数:
其中:
- 为一常数,一般取 1 即可。
- 为模数。
- 为整数到整数的映射,代码中使用了
xor shift
算法。
无根树
发现上述哈希值的定义方式天然支持换根操作。
于是可以考虑换根 DP 求得以所有节点为根的树的哈希值,再进行多重集哈希即得无根树的哈希值。
不过对于两棵无根树,更进一步地可观察得到一些性质:
- 它们的重心数量一定相等。
- 以重心为根的有根树一一对应。
于是可以考虑先求重心,构造出以重心为根的有根树哈希后比较哈希值,正确性有所上升。
AHU 算法
咕咕~
例题
UOJ #763. 树哈希
给定一棵以点 为根的树,你需要输出这棵树中最多能选出多少个互不同构的子树。
两棵有根树 、 同构当且仅当他们的大小相等,且存在一个顶点排列 使得在 中 是 的祖先当且仅当在 中 是 的祖先。
。
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
组数据,每组数据给定两棵大小为 的无根树,判断它们是否同构。
,。
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; }
写在最后
参考:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】