树同构/树哈希
P5043 【模板】树同构([BJOI2015]树的同构)
实际上我们想做的东西就是类似于数列哈希一样给同构的树算出一个同样的值来表示。
由于是无标号的,因此不能用类似于 Prufer 序列之类以来标号的东西转成序列然后变成序列哈希。
考虑怎么做。一种很优的方式是考虑类似于 DP 的东西。我们设以 \(u\) 为根的子树的哈希值是 \(f_u\),那么我们可以考虑一种有交换律的东西(这是为了保证交换子树的处理顺序后哈希值不变):
\[f_u \gets f_u+F(f_v)
\]
其中 \(F\) 是某种操作,\(v\) 是 \(u\) 的儿子。一般我们可以用类似于如下东西去做:
ll get(ll x){
	x^=bas;x>>=1,x<<=5,x>>=7,x<<=4;
	return x+5201314ull;
}
事实上上面的所有值都是可替换的。这么复杂是为了保证出题人猜不到你是怎么乱搞的根本卡不了,一般就是异或一个固定值+位运算+加上神秘数字。
当然为了叶子节点的哈希值不为 0 我们可以每次操作完后都给 \(f_u\) 加一个固定的数。写出来效果是这样的:
void dfs1(int u){
	f[u]=0;
	for(int v:q[u]){
		dfs1(v);f[u]+=get(f[v]);
	}
	f[u]+=add;
}
但是由于树的无标号的本质,因此我们需要换一下根。一个简单想法是求出每一个点作为根的哈希值然后对所有值取 \(\max\) 作为这棵树的哈希值。设 \(g_u\) 表示 \(u\) 为根的哈希值。由于有交换律,我们可以像正常 DP 一样写:
\[g_v=f_v+F(g_u-F(f_v))
\]
边界条件就是 \(g_{rt}=f_{rt}\)
于是直接算,然后用 map 记一下树的哈希值即可。
code
看起来比其他代码简洁很多。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll unsigned long long
const int N=55;
map <ll,int> mp;
vector <int> q[N];
mt19937 rnd(time(0));
int m,n;
ll f[N],g[N],bas=rnd(),add=rnd();
ll get(ll x){
	x^=bas;x>>=1,x<<=5,x>>=7,x<<=4;
	return x+5201314ull;
}
void dfs1(int u){
	f[u]=0;
	for(int v:q[u]){
		dfs1(v);f[u]+=get(f[v]);
	}
	f[u]+=add;
}
void dfs2(int u){
	for(int v:q[u]){
		g[v]=f[v]+get(g[u]-get(f[v]));dfs2(v);
	}
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>m;
	for(int T=1;T<=m;T++){
		cin>>n;
		for(int i=1,x;i<=n;i++)cin>>x,q[x].push_back(i);
		int rt=q[0][0];dfs1(rt);g[rt]=f[rt];dfs2(rt);
		sort(g+1,g+n+1);ll val=g[n];if(mp[val])cout<<mp[val]<<'\n';else mp[val]=T,cout<<T<<'\n';
		for(int i=0;i<=n;i++)q[i].clear(),f[i]=0;
	}
	return 0;
}
                    
                
                
            
        
浙公网安备 33010602011771号