zoukankan      html  css  js  c++  java
  • 树哈希学习笔记

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

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

    本文采用的Hash方式如下:

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

    模板

    //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
    ",i);
            }
            else{
                printf("%d
    ",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
    ",i);
                break;
            }
        }
    }
    

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

  • 相关阅读:
    IOS开发银行系统的四舍五入的算法
    线程通信之初认识
    多线程同步机制练习之银行存钱
    解决线程安全问题方式三:loke锁
    使用同步机制解决单例模式中的懒汉式的线程安全问题
    (对于继承Thread类)线程安全问题解决方式二:同步静态方法
    (对于实现Runnable接口)线程安全问题解决方式二:同步方法
    (对于继承Thread类)线程安全问题解决方式一:同步代码块
    (对于实现Runnable接口)线程安全问题解决方式一:同步代码块
    三个窗口卖票(实现Runnable方式创建线程)之线程安全问题
  • 原文地址:https://www.cnblogs.com/ucprer/p/13490839.html
Copyright © 2011-2022 走看看