【JSOI2016】【树hash】【树形dp换根】独特的树叶

5 篇文章 0 订阅
2 篇文章 0 订阅

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

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

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

【样例输入】
5
1 2
2 3
1 4
1 5
1 2
2 3
3 4
4 5
3 6
【样例输出】
1
【提示】
对于所有数据,1≤N≤10^5

【思路】

这道题也是一道好题。首先你需要学会树hash,其实这个知识点也不难,就是给予每个点一个值,这个值与子树形态有关,用于判断两颗树是否相同。比如这道题,我的hash函数就是:
f [ u ] = ⨁ v ϵ s o n [ u ] ( f [ v ] ∗ s e e d + s i z e [ v ] 2 ) f[u]=\bigoplus_{v\epsilon son[u]} (f[v]*seed+size[v]^2) f[u]=vϵson[u](f[v]seed+size[v]2)
显然,树形态如果相同,其根节点的hash值一定相同。所以我们对于这道题就有了一个暴力的思路,去掉每一个叶子,判断是否同构。但这样是 O ( n 2 ) O(n^2) O(n2)。因为我们需要知道A树以每一个点为根的hash值,这是 O ( n 2 ) O(n^2) O(n2)。这就是为什么这道题的hash函数最好与异或有关。因为这样进行换根dp时更方便。我们只需要去掉自己对父亲的影响,然后父亲此时的hash值就是当前点为根是父亲的hash值。这样我们对两棵树就都可以进行一次换根dp。然后再枚举叶子,去掉它对与之相连的点的影响,判断与之相连的点此时的hash在A树中是否出现过即可。注意,最好不要选择B树的叶子节点为根dp,否则去掉其对与之相连的点的影响的操作会比较不便。时间复杂度 O ( n ) O(n) O(n)
代码:

#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define re register
using namespace std;
const int N=2e5+5;
const int seed=5200827;
tr1::unordered_map<long long,bool>vis;
inline int red(){
    int data=0;int w=1; char ch=0;
    ch=getchar();
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
    return data*w;
}
int n,m,a,b,c,siz[N];
long long f[N],dp[N];
vector<int>g[N];
long long hash;
inline void add(const int&a,const int&b){g[a].push_back(b);g[b].push_back(a);}
void dfs1(int u,int fa){siz[u]=1;
	for(int re i=g[u].size()-1;~i;--i){
		int v=g[u][i];
		if(v==fa)continue;
		dfs1(v,u);siz[u]+=siz[v];
		f[u]^=(f[v]*seed+1ll*siz[v]*siz[v]);
	}
}void dfs2(int u,int fa){
	for(int re i=g[u].size()-1;~i;--i){
		int v=g[u][i];
		if(v==fa)continue;
		dp[v]=f[v]^((dp[u]^(f[v]*seed+1ll*siz[v]*siz[v]))*seed+1ll*(n+(u>n)-siz[v])*(n+(u>n)-siz[v]));
		dfs2(v,u);
	}
}
int main(){
	n=red();int s;
	if(n==1)return puts("1");
	for(int re i=1;i^n;++i)add(red(),red());
	for(int re i=0;i^n;++i)add(red()+n,red()+n);
	for(int re i=n+1;i<((n+1)<<1);++i)
		if(g[i].size()>1){s=i;break;}
	dfs1(1,0);dfs1(s,0);
	dp[1]=f[1];dp[s]=f[s];
	dfs2(1,0);dfs2(s,0);
	for(int re i=1;i<=n;i++)vis[dp[i]]=1;
	for(int re i=n+1;i<((n+1)<<1);++i){
		if(g[i].size()==1){
			int u=i,v=g[u][0];
			hash=dp[v]^(f[u]*seed+1);
			if(vis[hash])return printf("%d\n",u-n),0;
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值