loading...

树哈希与诶胡

树同构

对于 \(T_1, T_2\) 两棵树,如果能通过重新编号使 \(T_1, T_2\) 完全相同,则称 \(T_1,T_2\) 同构。

有根树同构判定

树哈希

构造如下哈希函数:

\[h_u=\sum_{fa_v=u} h_v \times p(\mathrm{siz}_v) \]

我们用子树哈希值乘上对于每个相同子树大小 \(\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;
}
posted @ 2025-03-04 12:56  goldspade  阅读(52)  评论(0)    收藏  举报