题目链接

这道题是一道判断无根树同构的模板题,判断同构主要的思路就是哈希。

一遇到哈希题,一百个人能有一百零一种哈希方式,这篇题解随便选用了一种——类似杨弋《Hash在信息学竞赛中的一类应用》中的这种,可能不是最简洁好写的,但是能用。

我的哈希规则:子树\(u\)的哈希值由它的每一个子树\(v_i\)的哈希值得来,首先将所有\(f(v)\)排个序(防止顺序不同造成影响),然后\(f(u) = size(u) * \sum_i f(v_i)W^{i - 1} \bmod P\),\(W\)是事先选取的一个位权,\(P\)是模数,\(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\)的每个兄弟\(v_i\)的\(f(v_i)\)得来。但是直接暴力枚举的话在菊花图上是\(O(n^2)\)的,那怎么办呢?

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

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

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

把A的所有哈希值存到一个set里,然后枚举B的每个度为1的点\(u\),求出以\(u\)为根它的唯一子树\(v\)的哈希值,如果set里有这个值,\(u\)就是所求的点之一。

代码比较丑,见谅 ><

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <set>
#define space putchar(' ')
#define enter putchar('\n')
typedef long long ll;
using namespace std;
template <class T>
void read(T &x){
char c;
bool op = 0;
while(c = getchar(), c < '0' || c > '9')
if(c == '-') op = 1;
x = c - '0';
while(c = getchar(), c >= '0' && c <= '9')
x = x * 10 + c - '0';
if(op) x = -x;
}
template <class T>
void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar('0' + x % 10);
} const int N = 100005, W = 1000000021, P = 999999137;
int n, m, fa[N], f[N], g[N], pw[N], Sze[N], deg[N], ans = P;
int ecnt, adj[N], nxt[2*N], go[2*N];
vector <int> son[N], sl[N], sr[N];
set <int> vis;
bool isB; void add(int u, int v){
if(isB) deg[u]++;
go[++ecnt] = v;
nxt[ecnt] = adj[u];
adj[u] = ecnt;
}
int dfs1(int u, int pre){
Sze[u] = 1;
fa[u] = pre;
son[u].clear();
for(int e = adj[u], v; e; e = nxt[e])
if((v = go[e]) != pre){
son[u].push_back(dfs1(v, u));
Sze[u] += Sze[v];
}
if(son[u].empty()) return f[u] = 1;
sort(son[u].begin(), son[u].end());
ll ret = 0;
for(int i = 0; i < (int)son[u].size(); i++)
ret = (ret * W + son[u][i]) % P;
return f[u] = Sze[u] * ret % P;
} void dfs2(int u){
if(fa[u]){
son[u].push_back(g[u]);
sort(son[u].begin(), son[u].end());
}
int sze = son[u].size();
sl[u].resize(sze);
sl[u][0] = son[u][0];
for(int i = 1; i < sze; i++)
sl[u][i] = ((ll)sl[u][i - 1] * W + son[u][i]) % P;
sr[u].resize(sze);
sr[u][sze - 1] = son[u][sze - 1];
for(int i = sze - 2; i >= 0; i--)
sr[u][i] = (sr[u][i + 1] + (ll)son[u][i] * pw[sze - i - 1]) % P;
for(int e = adj[u], v; e; e = nxt[e])
if((v = go[e]) != fa[u]){
if(sze == 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 < sze) g[v] = sr[u][p + 1];
if(p - 1 >= 0) g[v] = (g[v] + (ll)sl[u][p - 1] * pw[sze - 1 - p]) % P;
g[v] = (ll)g[v] * (n - Sze[v]) % P;
if(isB && deg[v] == 1 && vis.find(g[v]) != vis.end()) ans = min(ans, v);
dfs2(v);
}
if(!isB) vis.insert((ll)sl[u][sze - 1] * n % P);
} int main(){ pw[0] = 1;
for(int i = 1; i < N; i++)
pw[i] = (ll)pw[i - 1] * W % P;
read(n);
for(int i = 1, u, v; i < n; i++)
read(u), read(v), add(u, v), add(v, u);
dfs1(1, 0);
dfs2(1);
ecnt = 0, isB = 1, n++;
memset(adj, 0, sizeof(adj));
for(int i = 1, u, v; i < n; i++)
read(u), read(v), add(u, v), add(v, u);
dfs1(1, 0);
if(deg[1] == 1 && vis.find(f[go[adj[1]]]) != vis.end())
ans = 1;
dfs2(1);
write(ans), enter; return 0;
}

BZOJ 4754 [JSOI2016]独特的树叶 | 树哈希判同构的更多相关文章

  1. bzoj 4754: [Jsoi2016]独特的树叶

    不得不说这是神题. %%%   http://blog.csdn.net/samjia2000/article/details/51762811 #include <cstdio> #in ...

  2. bzoj4337: BJOI2015 树的同构 树哈希判同构

    题目链接 bzoj4337: BJOI2015 树的同构 题解 树哈希的一种方法 对于每各节点的哈希值为hash[x] = hash[sonk[x]] * p[k]; p为素数表 代码 #includ ...

  3. BZOJ4754 JSOI2016独特的树叶(哈希)

    判断两棵无根树是否同构只需要把重心提作根哈希即可.由于只添加了一个叶子,重心的位置几乎不发生偏移,所以直接把两棵树的重心提起来,逐层找哈希值不同且对应的两子树即可.被一个普及组子问题卡一年. #inc ...

  4. 【BZOJ4754】独特的树叶(哈希)

    [BZOJ4754]独特的树叶(哈希) 题面 BZOJ 给定一个\(n\)个节点的树A和一个\(n+1\)个节点的树\(B\) 求\(B\)的一个编号最小的节点,使得删去这个节点后\(A,B\)同构 ...

  5. P4323 [JSOI2016]独特的树叶(树哈希)

    传送门 树哈希?->这里 反正大概就是乱搞--的吧-- //minamoto #include<bits/stdc++.h> #define R register #define l ...

  6. BZOJ4754 & 洛谷4323 & LOJ2072:[JSOI2016]独特的树叶——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=4754 https://www.luogu.org/problemnew/show/P4323 ht ...

  7. bzoj4754[JSOI2016]独特的树叶

    这个题....别人写得怎么都....那么短啊? 我怎么....WA了好几次啊....怎么去loj扒了数据才调出来啊? 这个算法...怎么我还是不知道对不对啊 怎么回事啊怎么回事啊怎么回事啊? 请无视上 ...

  8. Luogu 4323 [JSOI2016]独特的树叶

    新技能get 树哈希,考虑到两棵树相同的条件,把每一个结点的哈希值和树的siz写进哈希值里去. 做出A树每一个结点为根时的树的哈希值丢进set中,然后暴力枚举B树中度数为1的点,求出删掉这个点之后的哈 ...

  9. [JSOI2016]独特的树叶

    https://zybuluo.com/ysner/note/1177340 题面 有一颗大小为\(n\)的树\(A\),现加上一个节点并打乱编号,形成树\(B\),询问加上的节点最后编号是多少? \ ...

随机推荐

  1. .Net Core 系列之一 hello world

    OS: win10 企业版 中文环境 .Net Core: 切记不要装64版本,否则可能会出现vs2017无法生成.net core 2.0的项目 dotnet-sdk-2.0.0-win-x86.e ...

  2. odoo 订餐系统之消息提醒

    打算入手odoo开发新的系统,先研究下开发的过程是如何的.案例模仿自带的订餐系统,此系统模块不多,但很典型,可以达到联系的目的.先记录下订餐系统消息提醒的开发过程. 1.添加自己的addons目录my ...

  3. js类型----你所不知道的JavaScript系列(5)

    ECMAScirpt 变量有两种不同的数据类型:基本类型,引用类型.也有其他的叫法,比如原始类型和对象类型等. 1.内置类型 JavaScript 有七种内置类型: • 空值(null) • 未定义( ...

  4. ActiveMQ 填坑记

    前言 MQ是现在大型系统架构中必不可少的一个重要中间件,之前有偏文章<MQ(消息队列)常见的应用场景解析>介绍过MQ的应用场景,现在流行的几个MQ是rabbitmq,rocketma,ka ...

  5. NodeMCU学习(二) : 如何使用NodeMCU进行开发

    NodeMCU的GPIO口 Arduino的引脚号与NodeMCU的GPIO口直接对应,NodeMCU的GPIO函数pinMode,  digitalWrite, DigitalRead也是和Ardu ...

  6. Python - 内置函数 选例

    概览参见  https://www.runoob.com/python/python-built-in-functions.html 官方文档 https://docs.python.org/3/li ...

  7. Oracle数据库设置为归档模式的操作方法

    Oracle归档模式非常非常重要!对于有些数据库刚装好后可能是非归档模式,这是很危险的!为了安全起见,一定要谨记:对于Oracle数据库,一定要设置为归档模式,尤其是生产库,只有这样才能实现数据库的有 ...

  8. Git常用命令梳理

    在日常的Git版本库管理工作中用到了很多操作命令,以下做一梳理: 查看分支列表,带有*的分支表示是当前所在分支 [root@115~~]#git branch 查看分支详细情况 (推荐这种方式) [r ...

  9. 集群环境删除redis指定的key

    1.说明 redis集群上有时候会需要删除多个key,就必须需要登录到每个节点上,而且有可能这个key不在这个节点,这样删除起来就比较麻烦,下面提供一种便捷方式可以实现 2.查看redis集群中的ma ...

  10. 第二次作业 对VC++6.0编译软件的评价

    首先这个软件伴随着我们很长时间了,它是我们一上大学最先接触的,也是应用相当多的一个软件,其实在最初的时候,我对编译软件的理解非常有限,觉得它能实现一个代码的功能十分神奇的一件事情,虽然彼时我们写的代码 ...