树哈希 & 浅学
介绍
树哈希是哈希的一种废话,是一种将一颗有跟无标号的树映射为一个数的方法。
可以类比字符串哈希。
我们观察一下字符串哈希:
看不懂?换成更常见的式子:
其中 \(base\) 表示进制, \(c\) 表示字符串,\(p\) 表示模数。
对应的 \(F()\) 表示哈希函数,\(G()\) 则是取模的函数。
在树哈希中,我们可以采取相同的操作:
但是,就止步于此了吗?
树哈希的哈希函数当然可以像字符串哈希那样搞一个进制,然后乘起来,但那太容易被卡了(第一个例题就会卡你这个),所以我们要重新设计哈希函数。
xor-shift
我们要使哈希函数不被卡,那必须使用我们自己都不知道能算出来什么的哈希函数。
我们大胆想一下,什么运算足够"随机"到我们都不知道会算出什么呢?
那就是按位异或(xor)看似无厘头却又自成逻辑,十分符合我们的需求。
当然只有异或显然还是不够的,于是我们又引入了随机数,我们在程序开头随机提取一个随机数当做类似进制的东西,对一个数进行按位异或,再左移右移瞎搞之后,哈希函数映射出来的东西终于不是人类所能理解的了。
这种使用按位异或的方法叫做 xor-shift。
例题
树哈希\(^{UOJ763}\)
一道板题,用set维护字数个数即可。
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int NUM=1e6+10;
set<ull> s;
int h[NUM],to[NUM<<1],nxt[NUM<<1],tot;
ull C,f[NUM],val;
int n;
void addedge(int x,int y){
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
}
//哈希函数,这里用的Cissia的名字
ull Cissia(ull x){
x^=val,x^=(x>>1),x^=(x<<5),x^=(x<<6);//瞎搞的左移右移,这里用的Cissia的身高。
x+=237051146237051146ull;
return x;
}
void dfs(int x,int fa){
f[x]=0;
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
if(y==fa) continue;
dfs(y,x);
f[x]+=Cissia(f[y]);
}
f[x]+=C;
s.insert(f[x]);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
srand(237051146);//种子用的Cissia的主色调色号。
cin>>n;
C=rand(),val=rand();
for(int i=1,x,y;i<n;++i){
cin>>x>>y;
addedge(x,y);
addedge(y,x);
}
dfs(1,0);
cout<<s.size();
return 0;
}
【模板】树同构([BJOI2015] 树的同构)\(^{luoguP5043}\)
我们可以使用换根DP求出以每个节点为跟的哈希值,然后你取最大值、最小值、平均值或中位数来表示这棵树,然后用map维护一下分类即可。
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int NUM=1e6+10;
mt19937 rnd(237051146);
set<ull> s;
int h[NUM],to[NUM<<1],nxt[NUM<<1],tot;
ull C,f[NUM],val,f2[NUM];
int n;
void addedge(int x,int y){
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
}
ull Cissia(ull x){
x^=val,x^=(x>>1),x^=(x<<6),x^=(x<<5);
x+=237051146237051146ull;
return x;
}
void dfs(int x,int fa){
f[x]=0;
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
if(y==fa) continue;
dfs(y,x);
f[x]+=Cissia(f[y]);
}
f[x]+=C;
s.insert(f[x]);
}
void dfs2(int x,int fa){
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
if(y==fa) continue;
f2[y]=f[y]+Cissia(f2[x]-Cissia(f[y]));
dfs2(y,x);
}
}
map<ull,int> mp;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
C=rnd(),val=rnd();
cin>>n;
for(int i=1,x;i<=n;++i){
cin>>x;
for(int j=1,y;j<=x;++j){
cin>>y;
if(y==0) continue;
addedge(y,j);
addedge(j,y);
}
dfs(1,0);
f2[1]=f[1];
dfs2(1,0);
ull res=0;
for(int j=1;j<=x;++j){
res=max(res,f2[j]);
}
if(!mp.count(res)){
cout<<i<<'\n';
mp[res]=i;
}else cout<<mp[res]<<'\n';
memset(h,0,sizeof(h));
memset(nxt,0,sizeof(nxt));
memset(to,0,sizeof(to));
tot=0;
}
return 0;
}
Symmetree\(^{CF1800G}\)
也可以说是一道水题,虽然这道题在洛谷上是紫题,CF上评级也才2200。
对于每一棵树跑一边树哈希,然后开始根节点检查是否对称。
这里需要注意,若剩下一个子节点,就需要继续检查这个剩下的子节点,当然因为我太蒻了,题解写的比我这简单得多。
具体细节请看代码:
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int NUM=2e5+10;
mt19937_64 rnd(237051146);
int h[NUM],to[NUM<<1],nxt[NUM<<1],tot;
ull C,f[NUM],val,f2[NUM];
int n;
void addedge(int x,int y){
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
}
ull Cissia(ull x){
x^=val,x^=(x>>1),x^=(x<<6),x^=(x<<5);
x+=237051146237051146ull;
return x;
}
void clear(){
memset(h,0,sizeof(h));
memset(nxt,0,sizeof(nxt));
memset(to,0,sizeof(to));
tot=0;
}
void dfs(int x,int fa){
f[x]=0;
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
if(y==fa) continue;
dfs(y,x);
f[x]+=Cissia(f[y]);
}
f[x]+=C;
}
bool check(int x,int fa){
set<ull> s;
map<ull,int> mp;
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
if(y==fa) continue;
if(mp[f[y]]!=0){
s.erase(f[y]);
mp[f[y]]=0;
}else{
s.insert(f[y]);
mp[f[y]]=y;
}
}
if(s.size()>=2) return 0;
if(s.size()==1) return check(mp[*s.begin()],x);
return 1;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
C=rnd(),val=rnd();
int t;
cin>>t;
while(t--){
cin>>n;
for(int i=1,x,y;i<n;++i){
cin>>x>>y;
addedge(x,y);
addedge(y,x);
}
dfs(1,0);
cout<<(check(1,0)?"YES\n":"NO\n");
clear();
}
return 0;
}
独特的树叶\(^{luoguP4323}\)
我们可以使用换根DP求出所有A树和B树所有节点为跟时的哈希值。
很容易发现在A树上shift一下然后在B树上减去那个常数,判一下是不是有相等就可以了。
如果不理解可以想一下你求树哈希的过程。
具体细节请看代码:
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int NUM=1e6+10;
mt19937 rnd(237051146);
set<ull> s;
int h[NUM],to[NUM<<1],nxt[NUM<<1],tot;
ull C,f[NUM],val,f2[NUM];
int n;
void addedge(int x,int y){
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
}
ull Cissia(ull x){
x^=val,x^=(x>>1),x^=(x<<6),x^=(x<<5);
x+=237051146237051146ull;
return x;
}
void dfs(int x,int fa){
f[x]=0;
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
if(y==fa) continue;
dfs(y,x);
f[x]+=Cissia(f[y]);
}
f[x]+=C;
}
void dfs2(int x,int fa){
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
if(y==fa) continue;
f2[y]=f[y]+Cissia(f2[x]-Cissia(f[y]));
dfs2(y,x);
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
C=rnd(),val=rnd();
cin>>n;
for(int i=1,x,y;i<n;++i){
cin>>x>>y;
addedge(x,y);
addedge(y,x);
}
dfs(1,0);
f2[1]=f[1];
dfs2(1,0);
for(int i=1;i<=n;++i) s.insert(Cissia(f2[i]));
memset(h,0,sizeof(h));
memset(nxt,0,sizeof(nxt));
memset(to,0,sizeof(to));tot=0;
for(int i=1,x,y;i<=n;++i){
cin>>x>>y;
addedge(x,y);
addedge(y,x);
}
dfs(1,0);
f2[1]=f[1];
dfs2(1,0);
for(int i=1;i<=n;++i){
if(s.find(f2[i]-C)!=s.end()){
cout<<i;
return 0;
}
}
return 0;
}
最后
马蜂奇特,不喜勿喷。

浙公网安备 33010602011771号