洛谷 P4323 [JSOI2016]独特的树叶 树hash

题目描述

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

输入输出格式

输入格式:
输入一行包含一个正整数 N N N。接下来 N − 1 N-1 N1 行,描述树 A A A,每行包含两个整数表示树 A A A 中的一条边; 接下来 N N N 行,描述树 B B B,每行包含两个整数表示树 B B B 中的一条边。

输出格式:
输出一行一个整数,表示树 B B B 中相比树 A A A 多余的那个叶子的编号。如果有多个符合要求的叶子,输出 B B B 中编号最小的那一个的编号。

输入输出样例

输入样例#1:
5
1 2
2 3
1 4
1 5
1 2
2 3
3 4
4 5
3 6
输出样例#1:
1

说明
对于所有数据, 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105

分析:
显然是树hash的题目。
对于树 A A A,我们要求出每一个点为根的hash值。树 B B B要求出每一个叶子为根的hash值。
可以使用二次dfs的方式求得。设 f [ i ] f[i] f[i]为当前节点的hash值, g [ i ] g[i] g[i]为以 i i i为根的子树hash值。
考虑一个点的hash值为所有儿子的 g [ j ] ∗ k e y + s i z e [ j ] g[j]*key+size[j] g[j]key+size[j]的异或和,其中 k e y key key为位值,这里不用加起来取模的方式,采用异或可以更好算 f f f。只要可以通过再异或一个儿子达到去掉的结果。
对于B来说,去掉某个叶子节点的hash值等于他父亲的hash值异或上这个去掉叶子的hash值。所以我们要选一个不是叶子的节点做根。

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#include <map>
#define LL long long

const int maxn=1e5+7;
const int key=10189488;
const LL mod=1e9+7;

using namespace std;

int n,x,y;
int deg[maxn];

map <int,int> h;

struct tree{
    int n,cnt;
    int size[maxn],ls[maxn];
    int f[maxn],g[maxn],fa[maxn];
    struct edge{
        int y,next;
    }e[maxn*2];
    void add(int x,int y)
    {
        e[++cnt]=(edge){y,ls[x]};
        ls[x]=cnt;
    }
    int get(int x,int y)
    {
        return ((LL)x*(LL)key+y)%mod;
    }
    void dfs1(int x,int F)
    {
        g[x]=size[x]=1;
        fa[x]=F;
        for (int i=ls[x];i>0;i=e[i].next)
        {
            int y=e[i].y;
            if (y==fa[x]) continue;
            dfs1(y,x);
            size[x]+=size[y];
            g[x]^=get(g[y],size[y]);
        }
    }
    void dfs2(int x)
    {
        if (!fa[x]) f[x]=g[x];
            else f[x]=g[x]^get(f[fa[x]]^get(g[x],size[x]),n-size[x]);
        for (int i=ls[x];i>0;i=e[i].next)
        {
            int y=e[i].y;
            if (y==fa[x]) continue;
            dfs2(y);
        }
    }
    int check(int x)
    {
        return f[fa[x]]^get(g[x],1);
    }
}A,B;

bool isleaf(int x)
{
    return deg[x]==1;
}

int main()
{
//	freopen("data.in","r",stdin);
    scanf("%d",&A.n);
    for (int i=1;i<A.n;i++)
    {
        scanf("%d%d",&x,&y);
        A.add(x,y);
        A.add(y,x);
    }
    A.dfs1(1,0),A.dfs2(1);	
    for (int i=1;i<=A.n;i++) h[A.f[i]]=1;
    B.n=A.n+1;
    for (int i=1;i<B.n;i++)
    {
        scanf("%d%d",&x,&y);
        B.add(x,y);
        B.add(y,x);
        deg[x]++,deg[y]++;
    }	
    for (int i=1;i<=B.n;i++)
    {
        if (!isleaf(i))
        {
            B.dfs1(i,0);
            B.dfs2(i);
            break;
        }
    }				
    for (int i=1;i<=B.n;i++)
    {
        if (isleaf(i))
        {
            int k=B.check(i);
            if (h[k])
            {
                printf("%d",i);
                break;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值