【BZOJ - 4754】独特的树叶(树哈希)

题干:

JYY有两棵树A和B:树A有N个点,编号为1到N;树B有N+1个点,编号为1到N+1。JYY知道树B恰好是由树A加上一个叶

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

 

Input

输入一行包含一个正整数N。

接下来N-1行,描述树A,每行包含两个整数表示树A中的一条边;

接下来N行,描述树B,每行包含两个整数表示树B中的一条边。

1≤N≤10^5

 

Output

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

那一个的编号

 

Sample Input

5 1 2 2 3 1 4 1 5 1 2 2 3 3 4 4 5 3 6

Sample Output

1

Hint

解题报告: 

哈希规则:子树u的哈希值由它的每一个子树vi的哈希值得来,首先将所有f(v)排个序(防止顺序不同造成影响),然后

f(u)=(size(u) \times \sum_i f(vi)W_{i-1} )mod MOD

W是事先选取的一个位权,MOD是模数,size(u)是子树u的大小。

这样DFS一遍可求出以1号节点为根时,所有子树的哈希值f(u)。

但是这是无根树,我们想求出以任意节点为根时整棵树的哈希值。

设fa[u]以1为根时u的父亲,则上面的f(u)也是以fa[u]为根时子树u的哈希值。

再求一个g(u)表示以u为根时子树fa[u]的哈希值。这个g(u)怎么求呢?再DFS一遍,对于每个节点,g(u)由g(fa[u])以及u的每个兄弟vi的f(vi)得来。但是直接暴力枚举的话在菊花图上是O(n^2)的,那怎么办呢?

对于每个节点u维护一个数组,存储它所有儿子的哈希值f(v),如果有父亲,则g(u)也在里面,把这个数组排好序,求出每个前缀的哈希值和每个后缀的哈希值。这时,以u为根时整棵树的哈希值就是整个数组的哈希值(再乘上子树大小n)。

此时求每个儿子v的g(v),就是从那个数组中间去掉f(v)后的哈希值,二分查找后把前缀哈希值和后缀哈希值拼起来就可以得到。记得乘上v为根时uu的size即n−size(v)。

这样就求出以每个节点为根的哈希值了。

把A的所有哈希值存到一个set里,然后枚举B的每个度为1的点u,求出以u为根它的唯一子树v的哈希值,如果set里有这个值,u就是所求的点之一。部分内容参考自:链接

首先明确一个问题,以u为根节点的树的Hash值=树的大小 * 子树Hash值的带权和 % MOD,也就是说这个根节点是以子树大小的身份参与到其中的。也就是HASH[u]其实重头戏在于u的孩子节点,而u在其中的作用只是size那一部分权重而已,其他的都与他无关。这是在做树的问题的时候,和其他问题不一样的一点,也是比较有特色的一点。 

g[v]代表以v为根时,子树u的Hash值。(也就是以v为根节点的 部分哈希和)那么g[v]由g[u]和v的一系列兄弟节点f[vi]带权得来。

AC代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define F first
#define S second
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
typedef pair<int,int> PII;
const int MAX = 2e5 + 5;
const ll MOD = 2000020331;
//const ll MOD = 2000004199;  用这个模数都没有问题!
const ll seed = 31;//
ll PW[MAX];
struct Edge {int v,ne;
} e[MAX];
int head[MAX],tot;
int n;
int deg[MAX];
bool isB;
void add(int u,int v) {if(isB) deg[u]++;e[++tot].v = v;e[tot].ne = head[u]; head[u] = tot;
}
int size[MAX],fa[MAX];
vector<ll> son[MAX],sl[MAX],sr[MAX];
ll f[MAX],g[MAX];
set<ll> vis;
ll dfs1(int cur,int rt) {ll res = 0;fa[cur] = rt; son[cur].clear();//必须要清空!! size[cur] = 1;for(int i = head[cur]; ~i; i = e[i].ne) {int v = e[i].v; if(v == rt) continue;ll tmp = dfs1(v,cur);son[cur].push_back(tmp);size[cur] += size[v]; }if(son[cur].empty()) return f[cur] = 1;//注意叶子结点的HASH值需要是1,而不能是0,也就是son数组中必须都是正数的HASH值。sort(son[cur].begin(),son[cur].end());int up = son[cur].size();for(int i = 0; i<up; i++) res = (res * seed + son[cur][i]) % MOD;	return f[cur] = size[cur] * res % MOD;
}
int ans;
void dfs2(int u) {if(fa[u]) {son[u].pb(g[u]);sort(son[u].begin(),son[u].end());}int up = son[u].size();sl[u].resize(up);sl[u][0] = son[u][0]; 	for(int i = 1; i<up; i++) {//好像不太能把初始化合并进来 sl[u][i] = (sl[u][i-1] * seed + son[u][i])%MOD;}sr[u].resize(up);sr[u][up-1] = son[u][up-1];for(int i = up-2; i>=0; i--) sr[u][i] = (sr[u][i+1] + son[u][i] * PW[up-i-1])%MOD; //其实不是求后缀的HASH值,而是前缀HASH的后缀和,所以要这么写 for(int i = head[u]; ~i; i = e[i].ne) {int v = e[i].v;if(v == fa[u]) continue;if(up == 1) {g[v] = 1; dfs2(v); break;}int p = lower_bound(son[u].begin(),son[u].end(),f[v]) - son[u].begin();g[v] = 0;if(p+1 < up) g[v] = sr[u][p+1];if(p-1 >= 0) g[v] = (g[v]+sl[u][p-1]*PW[up-1-p])%MOD;g[v] = g[v] * (n-size[v]) % MOD;if(isB && deg[v] == 1 && vis.find(g[v]) != vis.end()) ans = min(ans, v);dfs2(v);}if(!isB) vis.insert(sl[u][up - 1] * n % MOD);
}
int main()
{PW[0] = 1;for(int i = 1; i<MAX; i++) PW[i] = PW[i-1]*seed % MOD;cin>>n;memset(head,-1,sizeof head);for(int u,v,i = 1; i<n; i++) {cin>>u>>v;add(u,v);add(v,u);}dfs1(1,0);  dfs2(1);tot=0,isB=1,n++;memset(head,-1,sizeof head);for(int u,v,i = 1; i<n; i++) {cin>>u>>v;add(u,v);add(v,u);} dfs1(1,0);ans=1e9;if(deg[1] == 1 && vis.find(f[e[head[1]].v]) != vis.end()) ans = 1;dfs2(1);printf("%d\n",ans); return 0 ;
}

另一种及其简单的Hash方式:

我们只需要H_u = seed \times( \prod H_{v_i} + size[u])就可以了。

其中H_{v_i}是每一个子树的hash值
这个函数和上一个函数一样,支持换根,那么就不限于找重心了
那第一颗树所有的hash值丢进set中,第二颗树删除一个节点的hash值也可以用类似的方法弄出来。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define F first
#define S second
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
typedef pair<int,int> PII;
const int MAX = 2e5 + 5;
//const ll MOD = 2000020331;//这个模数在这个方法中可以直接扔掉了,因为根本不是质数所以肯定要不了,因为逆元的求法就不对。
const ll MOD = 2000004199;  //用这个模数都没有问题!
const ll seed = 13331;//可换! 233,2333,23333对于第二个模数都可以过
ll INV;
struct Edge {int v,ne;
} e[MAX];
int head[MAX],tot;
int n;
void adde(int u,int v) {e[++tot].v = v;e[tot].ne = head[u]; head[u] = tot;
}
inline ll qpow(ll a,ll b) {ll res = 1;while(b) {if(b&1) res = res * a % MOD;a = a *a % MOD;b >>= 1;		}return res;
}
inline ll add(ll x,ll y) {return (x+y)%MOD;}
inline ll mul(ll x,ll y) {return (x*y)%MOD;}
inline ll sub(ll x,ll y) {return (x-y+MOD)%MOD;}
ll H[MAX];
int size[MAX],du[MAX];
void dfs(int cur,int fa) {H[cur] = 1;size[cur] = 1;for(int i = head[cur]; ~i; i = e[i].ne) {int v = e[i].v;if(v == fa) continue;dfs(v,cur);size[cur] = add(size[cur],size[v]);H[cur] = mul(H[cur],H[v]);}H[cur] = add(H[cur],size[cur]);H[cur] = mul(H[cur],seed);
}
set<ll> ss;
bool isB;
void dfs1(int cur,int fa) {if(isB==0) ss.insert(H[cur]);//必须要先插入,因为根的情况。 ll Hall = mul(H[cur],INV),Hres;Hall = sub(Hall,n);for(int i = head[cur]; ~i; i = e[i].ne) {int v = e[i].v;if(v == fa) continue;Hres=mul(Hall,qpow(H[v],MOD-2));Hres=add(Hres,n-size[v]); Hres=mul(Hres,seed);//至此Hres代表除v的树的Hash值H[v]=mul(H[v],INV); H[v]=sub(H[v],size[v]);H[v]=mul(H[v],Hres);H[v]=add(H[v],n);H[v]=mul(H[v],seed);dfs1(v,cur);}
}
int main()
{INV = qpow(seed,MOD-2);cin>>n;tot=0;memset(head,-1,sizeof head);for(int u,v,i = 1; i<n; i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);dfs(1,0);dfs1(1,0);tot=0;memset(head,-1,sizeof head);n++;for(int u,v,i = 1; i<n; i++) {scanf("%d%d",&u,&v);du[u]++,du[v]++;adde(u,v);adde(v,u);}isB=1;dfs(1,0);dfs1(1,0);for(int i = 1; i<=n; i++) {if(du[i] == 1) {ll Hres = mul(H[i],INV);Hres = sub(Hres,n);if(ss.find(Hres) != ss.end()) {printf("%d\n",i);return 0 ;}}}return 0 ;
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/439731.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一步步编写操作系统 36 一级页表与虚拟地址1

为了给大家说清楚分页机制&#xff0c;我们先在宏观上说下cpu地址变换过程&#xff0c;先让大家有个直观的印象&#xff0c;如果有不明白的地方也不要着急&#xff0c;适时地不求甚解&#xff0c;有助于从全局上将知识融会贯通&#xff08;这句话是我即兴说的&#xff0c;说得多…

动手学无人驾驶(4):基于激光雷达点云数据3D目标检测

上一篇文章《动手学无人驾驶&#xff08;3&#xff09;&#xff1a;基于激光雷达3D多目标追踪》介绍了3D多目标追踪&#xff0c;多目标追踪里使用的传感器数据为激光雷达Lidar检测到的数据&#xff0c;本文就介绍如何基于激光雷达点云数据进行3D目标检测。 论文地址&#xff1a…

【BZOJ - 4337】BJOI2015 树的同构(树哈希)

题干&#xff1a; 树是一种很常见的数据结构。 我们把N个点&#xff0c;N-1条边的连通无向图称为树。 若将某个点作为根&#xff0c;从根开始遍历&#xff0c;则其它的点都有一个前驱&#xff0c;这个树就成为有根树。 对于两个树T1和T2&#xff0c;如果能够把树T1的所有点…

一步步编写操作系统 37 一级页表与虚拟地址2

接上节&#xff0c;分页机制是建立在分段机制之上&#xff0c;与其脱离不了干系&#xff0c;即使在分页机制下的进程也要先经过逻辑上的分段才行&#xff0c;每加载一个进程&#xff0c;操作系统按照进程中各段的起始范围&#xff0c;在进程自己的4GB虚拟地址空间中寻找可有空间…

PointNet:3D点集分类与分割深度学习模型

之前的一篇博客《动手学无人驾驶&#xff08;4&#xff09;&#xff1a;基于激光雷达点云数据3D目标检测》里介绍到了如何基于PointRCNN模型来进行3D目标检测&#xff0c;作者使用的主干网是PointNet&#xff0c;而PointNet又是基于PointNet来实现的。今天写的这篇博客就是对Po…

【POJ - 3281】Dining(拆点建图,网络流最大流)

题干&#xff1a; Cows are such finicky eaters. Each cow has a preference for certain foods and drinks, and she will consume no others. Farmer John has cooked fabulous meals for his cows, but he forgot to check his menu against their preferences. Although…

计算机视觉那些事儿(1):基本任务

本文主要介绍深度学习在计算机视觉领域&#xff08;Computer vision&#xff09;基本任务中的应用&#xff0c;包括分类、检测、分割&#xff08;语义与实体)。 目录 引言 分类&#xff08;Classification&#xff09; 目标检测&#xff08;Object Detection&#xff09; T…

一步步编写操作系统 38 一级页表与虚拟地址3

接上&#xff0c;页是地址空间的计量单位&#xff0c;并不是专属物理地址或线性地址&#xff0c;只要是4KB的地址空间都可以称为一页&#xff0c;所以线性地址的一页也要对应物理地址的一页。一页大小为4KB&#xff0c;这样一来&#xff0c;4GB地址空间被划分成4GB/4KB1M个页&a…

《Python编程:从入门到实践》速查表

本文是Python畅销书《Python&#xff1a;从入门到实践》速查表。 随书配套视频观看地址&#xff1a;https://www.bilibili.com/video/av35698354 目录 1.Overview 2.Lists 3.Dictionaries 4.If and While Loops 5.Functions 6.Classes 7.Files and Exceptions 8.Testin…

【HDU - 5963】朋友(博弈,思维,必胜态必败态,找规律)

题干&#xff1a; B君在围观一群男生和一群女生玩游戏&#xff0c;具体来说游戏是这样的&#xff1a; 给出一棵n个节点的树&#xff0c;这棵树的每条边有一个权值&#xff0c;这个权值只可能是0或1。 在一局游戏开始时&#xff0c;会确定一个节点作为根。接下来从女生开始&…

一步步编写操作系统 39 二级页表1

前面讲述了页表的原理&#xff0c;并以一级页表做为原型讲述了地址转换过程。既然有了一级页表&#xff0c;为什么还要搞个二级页表呢&#xff1f;理由如下&#xff1a; 一级页表中最多可容纳1M&#xff08;1048576&#xff09;个页表项&#xff0c;每个页表项是4字节&#xf…

PointNet++详解与代码

在之前的一篇文章《PointNet&#xff1a;3D点集分类与分割深度学习模型》中分析了PointNet网络是如何进行3D点云数据分类与分割的。但是PointNet存在的一个缺点是无法获得局部特征&#xff0c;这使得它很难对复杂场景进行分析。在PointNet中&#xff0c;作者通过两个主要的方法…

一步步编写操作系统 40 内存分页下用户程序与操作系统的关系

分页的第一步要准备好一个页表&#xff0c;我们的页表是什么样子呢&#xff1f;现在我们要设计一个页表啦。 设计页表其实就是设计内存布局&#xff0c;不过在规划内存布局之前&#xff0c;我们需要了解用户进程与操作系统之间的关系。 前面讲保护模式时&#xff0c;我们知道…

【HDU - 4055】Number String(dp,思维)

题干&#xff1a; The signature of a permutation is a string that is computed as follows: for each pair of consecutive elements of the permutation, write down the letter I (increasing) if the second element is greater than the first one, otherwise write do…

一步步编写操作系统 41 快表tlb 简介

分页机制虽然很灵活&#xff0c;但您也看到了&#xff0c;为了实现虚拟地址到物理地址的映射&#xff0c;过程还是有些麻烦的。先要从CR3寄存器中获取页目录表物理地址&#xff0c;然后用虚拟地址的高10位乘以4的积做为在页目录表中的偏移量去寻址目录项pde&#xff0c;从pde中…

【CodeForces - 731C】Socks(并查集,思维)

题干&#xff1a; Arseniy is already grown-up and independent. His mother decided to leave him alone for m days and left on a vacation. She have prepared a lot of food, left some money and washed all Arseniys clothes. Ten minutes before her leave she real…

50个最有用的Matplotlib数据分析与可视化图

本文介绍了数据分析与可视化中最有用的50个数据分析图&#xff0c;共分为7大类&#xff1a;Correlation、Deviation、RankIng、Distribution、Composition、Change、Groups 原文链接&#xff1a;https://www.machinelearningplus.com/plots/top-50-matplotlib-visualizations-t…

一步步编写操作系统 42 用c语言编写内核

在这之前&#xff0c;我们一直用汇编语言直接与机器对话&#xff0c;如果大家不知道这个世界上有高级语言的话&#xff0c;我想大家也不会觉得写汇编代码的过程很辛苦&#xff0c;哈哈&#xff0c;幸福确实是比较出来的。相对于汇编语言&#xff0c;用c 语言写内核是非常爽的事…

【CodeForces - 722C】Destroying Array(并查集,时光倒流)

题干&#xff1a; 给定一个有n个数的序列a1,a2, ..., an 你每次可以将序列中一个数删去&#xff0c;剩下的数会被分割为一段一段连续的数列 给定一个删除数的顺序&#xff0c;问在每次删除之后&#xff0c;剩下的连续的数列中&#xff0c;数列和的最大值为多少 Input 第…

视觉SLAM十四讲(1):预备知识

最近在学习高翔博士的《视觉SLAM十四讲》&#xff08;第二版&#xff09;&#xff0c;算是初学本书&#xff0c;配套资源还算蛮丰富的&#xff0c;有代码&#xff08;第一版和第二版都有&#xff09;&#xff0c;B站上也有高翔博士对第一版录制的讲解视频&#xff0c;真的是很贴…