P4323

· · 题解

题目大意

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

树 B 恰好是由树 A 加上一个叶子节点,然后将节点的编号打乱后得到的。

求这个多余的叶子的编号。

解题思路

树哈希 + 换根 DP + set。

树哈希

根据题意,判断一些树是否同构的时,我们常常把这些树转成哈希值储存起来,以降低复杂度。

树哈希是很灵活的,可以设计出各种各样的哈希方式;但是如果随意设计,很有可能是错误的,容易被卡。

树哈希的方法有很多种,可以参考 OI WIKI 上的博客。

以下介绍一类国家集训队奆老发明的容易实现且(目前)没有被卡掉的哈希编码方式。

定义哈希数组 Hash1_u 求出以 u 为根的子树的哈希值,编码方式为:

Hash1_u=\sum\limits Hash1_v \times P_{siz_v} 但以每个节点为根跑一遍 ```DFS``` 求哈希,时间复杂度为 $\Theta(n^2)$ 会超时。 ### 换根 DP 定义以 $v$ 为根时整棵树的哈希值为 $Hash2_v$。 显然的,我们可以通过 $Hash1$ 推导 $Hash2$ 的值: ```hash2[u]=hash1[u]+tmp*prm[n-siz[u]];``` 这样将求哈希的过程优化到 $\Theta(n)$。 具体实现看代码。 ## AC 代码(附有注释) ```cpp #include <iostream> #include <string.h> #include <set> #define int unsigned long long //哈希自然溢出 using namespace std; struct node{int to,nxt;}e[114514<<1]; int head[114514<<1],tot;//双向边 void add(int x,int y){ e[++tot]={y,head[x]}; head[x]=tot; } int prm[1919810<<1]; bool isp[1919810<<1]; void init(int x){ memset(isp,1,sizeof isp); prm[0]=0; for(register int i=2; i<=x; i++){ if (isp[i])prm[++prm[0]]=i; for(int j=1; j<=prm[0]&&i*prm[j]<=x; j++){ isp[i*prm[j]]=0; if (!(i%prm[j])) break; } }//初始化筛质数 } int n,siz[114514],deg[114514]; int hash1[114514],hash2[114514]; void dfs1(int u,int fa){ siz[u]=hash1[u]=1; for(register int i=head[u]; i; i=e[i].nxt){ int to=e[i].to; if(to==fa) continue; dfs1(to,u); siz[u]+=siz[to]; hash1[u]+=hash1[to]*prm[siz[to]]; } }//dfs1求Hash1 void dfs2(int u,int fa,int tmp){ hash2[u]=hash1[u]+tmp*prm[n-siz[u]]; for(int i=head[u]; i; i=e[i].nxt){ int to=e[i].to; if(to==fa) continue; dfs2(to,u,hash2[u]-hash1[to]*prm[siz[to]]); } }//dfs2求Hash2 set<int> s; signed main(){ ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); cin >> n; init(1299710);//第 1e5 个质数 int jj,fq; for(register int i=1; i<n; i++){ cin >> jj >> fq; add(jj,fq);add(fq,jj); }//建树A dfs1(1,0);dfs2(1,0,0); for(register int i=1; i<n; i++) s.insert(hash2[i]);//用set记录A的哈希值,以便logn查询。 memset(head,0,sizeof head);tot=0; n++;//B节点数为n+1 for(register int i=1;i<n;i++){ cin >> jj >> fq; add(jj,fq);add(fq,jj);//建树B deg[jj]++,deg[fq]++;//记录节点度数 } dfs1(1,0);dfs2(1,0,0); for(register int i=1; i<=n; i++){ if(deg[i]!=1) continue;//不是叶子 if(s.find(hash2[e[head[i]].to]-2)!=s.end()){ cout << i << endl;//与A同构 return 0; } } } ```