【Luogu P4323】独特的树叶(树哈希)

题目描述

JYY有两棵树 A 和 B :树 A 有 N 个点,编号为 1 到 N ;树 B 有 N+1 个节点,编号为 1 到 N+1

JYY 知道树 B 恰好是由树 A 加上一个叶节点,然后将节点的编号打乱后得到的。他想知道,这个多余的叶子到底是树 B 中的哪一个叶节点呢?

题解

题意:找到B中编号最小的点,使得去掉该点后B树和A树相同。

不会树哈希的先去看看我blog中的树的同构

做法:
树哈希。
首先我们这么想,先把树A给hash了。
再枚举B中的叶子,去掉后求出B的hash值,直接pd出解。

如果这么写,并且求重心用于hash的话,复杂度是: O ( n 2 )

考虑优化。
我们显然不能每次都重新hash一次,不然总是 O ( n 2 ) 的。

那么就考虑能否递推出每次树的hash值咯。
这样的话我们也不能用重心了。

所以我们要求出以各个点为根的树的哈希值。
然后你可以发现两棵树都是可以递推出以各个点为根的hash值的。

具体来说就是用异或(需要随便搭配另外的哈希技巧),这样就可以很简单递推了。

具体见代码。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unordered_set>
//#include<set>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=1e5+10;
typedef unsigned long long ull;
struct edge{int to,next;}a[N<<1];
int head[N];int cnt=0;int n;
inline void add(int x,int y){a[++cnt]=(edge){y,head[x]};head[x]=cnt;}
//本题不好用重心(要判断一棵树去掉一个点后的树与另一棵树是否相同)
//以不同点为根时树的哈希值不同,使用重心也是这个原因
//所以当不用重心时就应该把以所有点为根的树的哈希值依次算出,比较时只要有一个相同则两树同构

//因此树哈希应保证在选取相同点为根时的哈希值相同
unordered_set<ull> S;//无序set提高效率
//set<ull> S;
int size[N];ull hs[N];
const int P=1e9+7;
inline void Ghash(int u,int fa)
{
    size[u]=hs[u]=1;
    for(register int v,i=head[u];i;i=a[i].next)
    {
        v=a[i].to;if(v==fa) continue;Ghash(v,u);size[u]+=size[v];
        hs[u]^=hs[v]+17;//异或子树哈希(异或两次可取消异或,适合递推)
    }
    hs[u]+=(ull)(size[u])*P+1;return ;
}
inline void DFS1(int u,int fa)
{
    S.insert(hs[u]);
    for(register int v,i=head[u];i;i=a[i].next)
    {
        v=a[i].to;if(v==fa) continue;
        register ull tmp=((hs[u]-(ull)(n)*P-1)^(hs[v]+17))+(ull)(n-size[v])*P+18;
        hs[v]=((hs[v]-(ull)(size[v])*P-1)^tmp)+(ull)(n)*P+1;DFS1(v,u);
    }
    return;
}
#define INF 2147483647
int du[N];int ans=INF;
inline void DFS2(int u,int fa)
{
    for(register int v,i=head[u];i;i=a[i].next)
    {
        v=a[i].to;if(v==fa) continue;
        if(du[v]==1){//是叶子就尝试去掉
            register ull res=((hs[u]-(ull)(n+1)*P-1)^(hs[v]+17))+(ull)(n)*P+1;
            if(S.count(res)) ans=min(ans,v);
        }
        else{//不是叶子就和上面一样
            register ull tmp=((hs[u]-(ull)(n+1)*P-1)^(hs[v]+17))+(ull)(n+1-size[v])*P+18;
            hs[v]=((hs[v]-(ull)(size[v])*P-1)^tmp)+(ull)(n+1)*P+1;DFS2(v,u);
        }
    }
    return;
}
int main()
{
    scanf("%d",&n);register int x,y;
    for(register int i=1;i< n;++i){scanf("%d%d",&x,&y);add(x,y);add(y,x);}
    Ghash(1,0);DFS1(1,0);//通过递推算以各个点为根时的哈希值
    Set(head,0);cnt=0;
    for(register int i=1;i<=n;++i){scanf("%d%d",&x,&y);add(x,y);add(y,x);du[x]++;du[y]++;}
    //要递推去掉叶子后的树的哈希值,故不能以叶子为根进行第一次计算
    for(x=1;x<=n;++x) if(du[x]>1) break;
    Ghash(x,0);DFS2(x,0);
    printf("%d\n",ans);
}

猜你喜欢

转载自blog.csdn.net/element_hero/article/details/80616126