JZOJ5454. 【NOIP2017提高A组冲刺11.5】仔细的检查 树hash

本文介绍了一种基于树结构的Hash算法实现,通过将树转换为有根树,并使用特定的Hash函数来验证两棵树是否相同。文章详细解释了如何通过寻找树的重心并将其作为根节点来简化问题,随后介绍了树Hash的具体实现过程,包括如何计算每个节点的Hash值以及如何通过排序和比较来确定两棵树是否等价。

Description

nodgd家里种了一棵树,有一天nodgd比较无聊,就把这棵树画在了一张纸上。另一天nodgd更无聊,就又画了一张。
这时nodgd发现,两次画的顺序是不一样的,这就导致了原本的某一个节点u0在第一幅图中编号为u1,在第二副图中编号为u2。
于是,nodgd决定检查一下他画出的两棵树到底是不是一样的。nodgd已经给每棵树的节点都从1到n进行了编号,即每棵树n个节点。
如果存在一个1到n的排列p1p2…pn,对于第一幅图中的任意一条边(u,v),在第二幅图中都能找到一条边(pu,pv),则认为这两幅图中的树是一样的。

知道是树hash,但是从来没写过这个玩意儿,没有信心写出来,然后想了个水法只捞到10分。。
其实是树hash的模板题。。无根树转有根树是经典操作了,把重心当根,超过1个重心就直接新建一个点做为根,注意要把重心拉上去,重心之间的边显然不能走。然后处理完两棵树以后直接树hash,具体的话就是:

(((hash[x]*=base)^=tmp[i])+=tmp[i])^=tmp[i];

从下往上hash就可以了,注意base不能太小,最好不要模,冲突可能性比较大,还是自然溢出比较好,。
然后方案的话扫一遍用hash来映射就ok了。
一个小错误导致WA半天。。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define mp make_pair
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std; 
int n,m;
const int N=4e5+5;
typedef long long ll;
const ll base=1e9+7;
int root,root1,root2;
int tot,head[N],f[N],size[N],ans[N],next[N],go[N],del[N];
ll hash[N],tmp[N];
pair<ll,int>q1[N],q2[N];
int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

inline void add(int x,int y)
{
    go[++tot]=y;next[tot]=head[x];head[x]=tot;
    go[++tot]=x;next[tot]=head[y];head[y]=tot;
}
inline void getroot(int x,int fa)
{
    size[x]=1;
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v==fa||del[i])continue;
        getroot(v,x);
        size[x]+=size[v];
        f[x]=max(f[x],size[v]);
    }
    f[x]=max(f[x],n-size[x]);
    if (!root||f[x]<f[root])root=x;
}
inline int gethash(int x,int fa)
{
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v==fa||del[i])continue;
        gethash(v,x);
    }
    int cnt=0;
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa&&!del[i])tmp[++cnt]=hash[v];
    }
    sort(tmp+1,tmp+cnt+1);
    hash[x]=base;
    fo(i,1,cnt)
     (((hash[x]*=base)^=tmp[i])+=tmp[i])^=tmp[i];
}
inline void solve1()
{
    fo(i,1,n-1)
    {
        int x,y;
        x=read(),y=read();
        add(x,y);
    }
    root=0;
    int r1=0,r2=0;
    getroot(1,0);
    fo(i,1,n)if (f[i]==f[root])r2=r1,r1=i;
    if (r2)
    {
        for(int i=2;i<=tot;i+=2)
        {
            int v=go[i];
            if (go[i]==r1&&go[i^1]==r2||go[i]==r2&&go[i^1]==r1)
            {
                del[i]=del[i^1]=1;
                break;
            }
        }
        add(n*2+1,r1);add(n*2+1,r2);
        root=n*2+1;
    }
    root1=root;
    gethash(root,0);
}
inline void solve2()
{
    fo(i,1,n-1)
    {
        int x,y;
        x=read(),y=read();
        add(x+n,y+n);
    }
    root=0;
    int r1=0,r2=0;
    getroot(n+1,0);
    fo(i,n+1,n+n)
    if (f[i]==f[root])r2=r1,r1=i;
    if (r2)
    {
        for(int i=2;i<=tot;i+=2)
        {
            int v=go[i];
            if (go[i]==r1&&go[i^1]==r2||go[i]==r2&&go[i^1]==r1)
            {
                del[i]=del[i^1]=1;
                break;
            }   
        }
        add(n*2+2,r1);add(n*2+2,r2);
        root=n*2+2;
    }
    root2=root;
    gethash(root,0);
}
inline void getans(int x1,int x2,int fa1,int fa2)
{
    int tot1=0,tot2=0;
    for(int i=head[x1];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa1&&!del[i])q1[++tot1]=mp(hash[v],v);
    }
    for(int i=head[x2];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa2&&!del[i])q2[++tot2]=mp(hash[v],v);
    }
    sort(q1+1,q1+1+tot1);
    sort(q2+1,q2+1+tot2);
    fo(i,1,tot1)ans[q1[i].second]=q2[i].second;
    for(int i=head[x1];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa1&&!del[i])getans(v,ans[v],x1,x2);
    }
}
int main()
{
    freopen("check.in","r",stdin);
    freopen("check.out","w",stdout);
    scanf("%d",&n);
    tot=1;
    solve1();
    solve2();
    if (hash[root1]!=hash[root2])
    {
        printf("NO\n");
        return 0;
    }
    ans[root1]=root2;
    getans(root1,root2,0,0);
    printf("YES\n");
    fo(i,1,n)printf("%d ",ans[i]-n);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值