树哈希学习笔记

我们有时需要判断一些树是否同构。这时,选择恰当的Hash方式来将树映射成一个便于储存的Hash值(一般是 32 位或 64 位整数)是一个优秀的方案。

树Hash定义在有根树上。判断无根树同构的时候,可以比较重心为根的Hash值(一个树最多有两个根)或者比较每个点为根的Hash值(后者有O(n)的求解方法)。

本文采用的Hash方式如下:

\(f_{x}=1+\sum_{y \in s o n_{x}} f_{y} \times \operatorname{prime}\left(\operatorname{size}_{y}\right)\)

模板

//dfs1:求一个点为根时所有点子树的哈希值;
//dfs2:求每个点为根时该点的哈希值;
struct Tree{
    struct edge{
        int v,next;
    }E[maxm];
    int head[maxn],tot;
    void addedge(int u,int v){
        E[++tot].v=v;
        E[tot].next=head[u];
        head[u]=tot;
    }
    ll hx[maxn],sz2[maxn];
    void dfs1(int u,int fa){
        sz2[u]=hx[u]=1;
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].v;
            if(v==fa)continue;
            dfs1(v,u);
            hx[u]+=hx[v]*Prime[sz2[v]];
            sz2[u]+=sz2[v];
        }
    }
    ll ghx[maxn];
    void dfs2(int u,int fa,ll faf,int n){//faf:fa除去u子树,剩余树的哈希值
        ghx[u]=hx[u]+faf*(Prime[n-sz2[u]]);
        faf*=Prime[n-sz2[u]];//此时,faf=以u为根fa方向的树的哈希值-1
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].v;
            if(v!=fa){
                dfs2(v,u,faf+hx[u]-hx[v]*(Prime[sz2[v]]),n);//hx[u]-hx[v]*(Prime[size[v]):以u为根,除去fa和v方向的子树,剩余的哈希值
            }
        }
    }
}

例题

P5043无根树同构

题意:按顺序给出一些树,输出在这之前和该树同构的最早出现的树

解法:找出每棵树的重心(最多两个),该树的哈希值就是以两个重心为根的哈希值的最大值(或者最小值)

//按顺序给出一些树,输出在这之前和该树同构的最早出现的树
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=55;
const int maxm=105;
const int INF=1e8;
const ll llINF=1e18;

const int maxN=1e6+5;
const int maxp=1e6+5;
bool isPrime[maxN];
int Prime[maxp], primecnt = 0;
void GetPrime(int n){//筛到n
	memset(isPrime, 1, sizeof(isPrime));
	isPrime[1] = 0;//1不是素数
	for(int i = 2; i <= n; i++){
		if(isPrime[i])//没筛掉
			Prime[++primecnt] = i; //i成为下一个素数
		for(int j = 1; j <= primecnt && i*Prime[j] <= n; j++) {
			isPrime[i*Prime[j]] = 0;
			if(i % Prime[j] == 0)//i中也含有Prime[j]这个因子
				break;
		}
	}
}

struct edge{
    int v,next;
}E[maxm];
int head[maxn],tot;
void addedge(int u,int v){
    E[++tot].v=v;
    E[tot].next=head[u];
    head[u]=tot;
}
int sz[maxn],maxnum[maxn],minn;
void dfs(int u,int fa,int n){
    sz[u]=1;
    int res=0;
    for(int i=head[u];i;i=E[i].next){
        int v=E[i].v;
        if(v==fa) continue;
        dfs(v,u,n);
        sz[u]+=sz[v];
        res=max(res,sz[v]);
    }
    res=max(res,n-sz[u]);
    maxnum[u]=res;
    minn=min(minn,maxnum[u]);
}
ll hx[maxn],sz2[maxn];
void dfsh(int u,int fa){
    sz2[u]=hx[u]=1;
    for(int i=head[u];i;i=E[i].next){
        int v=E[i].v;
        if(v==fa)continue;
        dfsh(v,u);
        hx[u]+=hx[v]*Prime[sz2[v]];
        sz2[u]+=sz2[v];
    }
}
void getHash(int u){
    dfsh(u,0);
}
ll ans[maxn];
void init(int n){
    minn=INF;
    tot=0;
    memset(head,0,sizeof(head));
}
int main () {
    GetPrime(maxN-5);
    int M;
    scanf("%d",&M);
    map<ll,int>vis;
    for(int i=1;i<=M;i++){
        int n;
        scanf("%d",&n);
        init(n);
        for(int u=1;u<=n;u++){
            int fa;
            scanf("%d",&fa);
            if(fa!=0){
                addedge(u,fa);
                addedge(fa,u);
            }
        }
        dfs(1,0,n);
        vector<int>rt;
        for(int u=1;u<=n;u++){
            if(maxnum[u]==minn){
                rt.push_back(u);
            }
        }
        for(auto u:rt){
            getHash(u);
            ans[i]=max(ans[i],hx[u]);
        }
        if(!vis.count(ans[i])){
            vis[ans[i]]=i;
            printf("%d\n",i);
        }
        else{
            printf("%d\n",vis[ans[i]]);
        }
    }
}

P4323 每个点哈希

题意:给出一棵树a,大小为n,给出另一个大小为n+1的树b,b是在a树的基础上加上一个叶子节点,并打乱节点顺序,问b树中编号最小的可能是新加入点的点。

思路:对于a树的所有点,处理出以其为根的hash值,存在set中。同样预处理b树中所有点以其为根的hash值,对于b树中所有的和叶子节点相邻的点,若去掉相邻的叶子节点,以其为根的哈希值就会-2,判断-2后的哈希值是否在set中即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int maxm=2e5+5;
const int INF=1e8;

const int maxN=2e6+5;
const int maxp=2e6+5;
bool isPrime[maxN];
int Prime[maxp], primecnt = 0;
void GetPrime(int n){//筛到n
	memset(isPrime, 1, sizeof(isPrime));
	isPrime[1] = 0;//1不是素数
	for(int i = 2; i <= n; i++){
		if(isPrime[i])//没筛掉
			Prime[++primecnt] = i; //i成为下一个素数
		for(int j = 1; j <= primecnt && i*Prime[j] <= n; j++) {
			isPrime[i*Prime[j]] = 0;
			if(i % Prime[j] == 0)//i中也含有Prime[j]这个因子
				break;
		}
	}
}

struct Tree{
    struct edge{
        int v,next;
    }E[maxm];
    int head[maxn],tot;
    void addedge(int u,int v){
        E[++tot].v=v;
        E[tot].next=head[u];
        head[u]=tot;
    }
    ll hx[maxn],sz2[maxn];
    void dfs1(int u,int fa){
        sz2[u]=hx[u]=1;
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].v;
            if(v==fa)continue;
            dfs1(v,u);
            hx[u]+=hx[v]*Prime[sz2[v]];
            sz2[u]+=sz2[v];
        }
    }
    ll ghx[maxn];
    void dfs2(int u,int fa,ll faf,int n){//faf:fa除去u子树,剩余树的哈希值
        ghx[u]=hx[u]+faf*(Prime[n-sz2[u]]);
        faf*=Prime[n-sz2[u]];//此时,faf=以u为根fa方向的树的哈希值-1
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].v;
            if(v!=fa){
                dfs2(v,u,faf+hx[u]-hx[v]*(Prime[sz2[v]]),n);//hx[u]-hx[v]*(Prime[size[v]):以u为根,除去fa和v方向的子树,剩余的哈希值
            }
        }
    }
}t1,t2;
int du[maxn],near[maxn];
int main () {
    GetPrime(maxN-5);
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n-1;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        t1.addedge(u,v);
        t1.addedge(v,u);
    }
    for(int i=1;i<=n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        du[u]++;du[v]++;
        near[u]=v,near[v]=u;//near存一个相邻点,对于叶子节点相邻点只有一个
        t2.addedge(u,v);
        t2.addedge(v,u);
    }
    t1.dfs1(1,0);
    t1.dfs2(1,0,0,n);
    set<int>vis;
    for(int i=1;i<=n;i++){
        vis.insert(t1.ghx[i]);
    }
    t2.dfs1(1,0);
    t2.dfs2(1,0,0,n+1);
    for(int i=1;i<=n+1;i++){
        if(du[i]!=1)continue;
        ll temp=t2.ghx[near[i]]-2;
        if(vis.count(temp)){
            printf("%d\n",i);
            break;
        }
    }
}

参考与引用: https://www.cnblogs.com/kangkang-/p/11581726.html

posted @ 2020-08-12 15:30  UCPRER  阅读(301)  评论(0编辑  收藏  举报