「JSOI2016」独特的树叶(树Hash)
opening words
这是我在学玩 树hash 后来做的一道题,似乎 树hash 的题很少,我只找到了这一道,但是却是道省选题,说明省选还是可能出的。
problem outline
给定两棵树,其中一棵为 \(n\) 个节点,另一棵树是在上一棵树的基础上新增了一个叶子节点构成的(一共有 \(n+1\) 个节点),请你找出新增的是哪个叶子节点,输出编号最小的那一个。 (\(n <= 10^5\))
problem solution
这道题显然是个树同构的题目吧,所以需要用到 树hash。
因为是无根树,所以我们先利用换根DP求出第一棵树的每个节点做根时的Hash值,用 map 存起来。
然后对于第二棵树做同样的操作。
我们发现叶子节点的度数一定是 1,所以我们可以找到哪些是叶子节点,我们发现去掉叶子节点 \(u\) 的整棵树的hash值就是 \(ha[fa[u]] - base*pri[1]\)。
然后我们就可以在 map 里面找是否可以匹配。
problem std
// by longdie
#include <bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N = 1e5 + 5;
const ull base = 1;
map<ull, int> p;
int n, vis[N*20], tot, pri[N];
struct Tree {
ull f[N], ha[N];
int n, head[N], cnt, pa[N], d[N], siz[N];
struct edge { int to, next; } e[N<<1];
void add(int x, int y) {
e[++cnt] = (edge){y, head[x]}, head[x] = cnt, d[x]++;
}
void dfs0(int u, int fa) {
siz[u] = 1, ha[u] = base, pa[u] = fa;
for(register int i = head[u], v; i; i = e[i].next) {
v = e[i].to;
if(v == fa) continue;
dfs0(v, u);
siz[u] += siz[v];
ha[u] += ha[v] * pri[siz[v]];
}
}
void dfs1(int u, int fa) {
if(u != 1) {
f[u] = ha[u] + (f[fa] - ha[u]*pri[siz[u]])*pri[n-siz[u]];
}
for(register int i = head[u], v; i; i = e[i].next) {
v = e[i].to;
if(v == fa) continue;
dfs1(v, u);
}
}
void solve(int m) {
n = m;
for(register int i = 1, x, y; i < n; ++i) {
scanf("%d%d", &x, &y), add(x, y), add(y, x);
}
dfs0(1, 0);
f[1] = ha[1];
dfs1(1, 0);
}
} a, b;
signed main() {
scanf("%d", &n);
for(register int i = 2; i <= 2e6 && tot <= n+1; ++i) {
if(!vis[i]) pri[++tot] = i;
for(register int j = 1; j <= tot && pri[j]*i <= 2e6; ++j) {
vis[i * pri[j]] = 1;
if(i % pri[j] == 0) break;
}
}
a.solve(n);
for(register int i = 1; i <= n; ++i) {
p[a.f[i]] = 1;
}
b.solve(n + 1);
for(register int i = 1; i <= n + 1; ++i) {
if(b.d[i] != 1) continue;
int u = b.e[b.head[i]].to;
ull tmp = b.f[u] - 2;
if(p.find(tmp) != p.end()) return printf("%d\n", i), 0;
}
return 0;
}