树哈希与诶胡
树同构
对于 \(T_1, T_2\) 两棵树,如果能通过重新编号使 \(T_1, T_2\) 完全相同,则称 \(T_1,T_2\) 同构。
有根树同构判定
树哈希
构造如下哈希函数:
我们用子树哈希值乘上对于每个相同子树大小 \(\mathrm{siz}_v\) 具有相同值的 \(p(\mathrm{siz}_v)\),显然这样做可以使哈希值与每个子树遍历顺序无关。
\(p(v)\) 理应满足 \(\forall u \neq v:p(u)\neq p(v)\)。我们一般取 \(p(i)\) 为第 \(i\) 个质数,并随机打乱 \(p(i)\)。若两个树 \(T_1,T_2\) 的根节点哈希值相同,就有极大概率两树同构;反之则一定不同构。时间复杂度为 \(\mathcal O(n)\)。
缺陷:哈希冲突仍然存在较大可能性。
优点:效率高且灵活,利于无根树的换根 \(\rm DP\)。
AHU 算法
可以使用括号序列 \(h_u\) 来描述一个有根,且一棵每个节点的儿子有序,无标号的树,可以证明这样是一一对应的。
\(h(u)\) 构造方法如下:
- 叶子节点的括号序列为 \(()\)
- 对于一个有 \(k\) 个儿子的节点,设第 \(i\) 个儿子为 \(s_i\),则 \(h_u=(+\ h_{s_1}+\dots+h_{s_k}+)\),\(+\) 表示字符串拼接操作。\((\)、\()\) 分别表示单个左括号和单个右括号。
同样,我们可以用这样的括号序列还原出一个树。将这样的树随机打乱儿子顺序后,与原树仍然同构,但括号序列不同,我们找到这些括号序列中字典序最小的,就可以找到这些同构的树共用的一个表示方法,称这个括号序列为这棵树的最小表示法。
那么,两棵树同构当且仅当它们的最小表示相同。采用 2 进制压位,就可以实现一个 \(\mathcal O(\dfrac{n^2 \log n}\omega)\) 的确定性算法。效率相当的低,因此也可以用字符串哈希做到 \(O(n \log n)\)。这种方法具有与字符串哈希一样高准确率的特点,并且很快。实际上还有一个 \(\mathcal O(n \log n)\) 的确定性算法,但太难写了。
缺陷:一点都不灵活
优点:准确率可以达到 \(100\%\);用字符串哈希极难被卡掉。(毕竟还可以加双哈希,
十哈希,百哈希)。
无根树同构判定
只需要找到一个同位点即可,选取树的重心,当作有根树跑即可。
在无根树里,树哈希冲突概率很大,因此要把所有节点为根时根节点的哈希值排序后一一比对,若存在不相等的情况就不同构。
AHU 模板 of luogu P5043
这是一个使用压位的范例。
点击查看代码
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio (0), cin.tie (0), cout.tie (0)
#define rep(i, l, r) for (int i = l; i <= r; i ++)
#define per(i, r, l) for (int i = r; i >= l; i --)
#define LL long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
using namespace std;
const int N = 105, mod = 998244353;
int n, m;
map<__int128, int> mp[N];
vector<int> G[N];
#define adde(u, v) G[u].push_back(v)
int sz[N], f[N], ans=N;
void dfs(int x, int fa) {
sz[x] = 1, f[x] = 0;
for (auto y : G[x]){
if (y == fa) continue;
dfs(y, x);
sz[x] += sz[y];
f[x] = max(sz[y], f[x]);
}
f[x] = max(f[x], n-sz[x]);
ans = min(ans, f[x]);
}
__int128 h[N];
void H(int x, int fa) {
h[x] = 1, sz[x] = 1;
for (auto y : G[x]) {
if (y == fa) continue;
H(y, x), sz[x] += sz[y];
}
sort(G[x].begin(), G[x].end(), [](const int& u, const int& v) {
return h[u] < h[v];
});
for (auto y : G[x]) {
if (y == fa) continue;
h[x] = h[x]<<(sz[y]<<1)|h[y];
}
h[x] = h[x]<<1;
}
int main () {
FASTIO;
cin >> m;
rep(i, 1, m) {
cin >> n;
rep(i, 1, n) {
int f;
cin >> f;
if (f) adde(f, i), adde(i, f);
}
ans = N;
dfs(1, 0);
__int128 res = -1;
rep(i, 1, n)
if (f[i] == ans)
H(i, 0), res = res==-1?h[i]:min(res, h[i]);
if (mp[n].find(res)==mp[n].end()) mp[n][res]=i, cout << i << '\n';
else cout << mp[n][res] << '\n';
rep(i, 1, n) G[i].clear();
}
return 0;
}
AHU 模板 of SPOJ TREEISO: Tree Isomorphism
这是一个字符串哈希的范例。
点击查看代码
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio (0), cin.tie (0), cout.tie (0)
#define rep(i, l, r) for (int i = l; i <= r; i ++)
#define per(i, r, l) for (int i = r; i >= l; i --)
#define LL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define adde(G, u, v) G[u].push_back(v)
using namespace std;
const int N = 1e5+5, B = 131, mod = 998244353;
int n;
LL bf[N];
int sz[N], ans;
vector<int> we;
inline void getrt(vector<vector<int> >& G, int x, int fa) {
sz[x] = 1;
int res = 0;
for (auto y : G[x]) {
if (y == fa) continue;
getrt(G, y, x);
sz[x] += sz[y];
res = max(res, sz[y]);
}
res = max(res, n-sz[x]);
if (ans > res) ans = res, we.clear();
if (ans == res) we.emplace_back(x);
}
LL f[N];
inline void gethash(vector<vector<int> >& G, int x, int fa) {
f[x] = 1, sz[x] = 1;
for (auto y : G[x])
if (y != fa) gethash(G, y, x), sz[x] += sz[y];
stable_sort(G[x].begin(), G[x].end(), [](const int &x, const int& y) {
return f[x] < f[y];
});
for (auto y : G[x])
if (y != fa) f[x] = (f[x]*bf[sz[y]]+f[y])%mod;
(f[x] *= B)%=mod;
}
inline void solve() {
cin >> n;
vector<vector<int> > A(N), B(N);
rep(i, 2, n) {
int u, v;
cin >> u >> v;
adde(A, u, v), adde(A, v, u);
}
rep(i, 2, n) {
int u, v;
cin >> u >> v;
adde(B, u, v), adde(B, v, u);
}
ans = n, getrt(A, 1, 0);
LL ansA = -1, ansB = -1, ansA2 = 0, ansB2 = 0;
for (auto x : we) {
gethash(A, x, 0);
ansA = min(ansA, f[x]);
ansA2 = max(ansA2, f[x]);
}
ans = n, getrt(B, 1, 0);
for (auto x : we) {
gethash(B, x, 0);
ansB = min(ansB, f[x]);
ansB2 = max(ansB2, f[x]);
}
if (ansA == ansB && ansA2 == ansB2) cout << "YES\n";
else cout << "NO\n";
}
int main () {
FASTIO;
int _; cin >> _;
bf[0] = 1;
rep(i, 1, 100000) bf[i] = bf[i-1]*B;
while (_--)
solve();
return 0;
}

浙公网安备 33010602011771号