给定两棵无根树 (A, B) ,其中 (B) 恰好是由 (A) 添加一个叶子,然后将节点的编号打乱得到的,求这个被添加的叶子在 (B) 中的编号(若有多个符合要求的叶子,输出编号最小的)。
(|A|leq10^5)
hash,dp,二次换根
考虑使用树hash来快速比较,我以前用的hash式子是 (hash_u=(displaystylesum_{vin ext{son(u)}}base^{k-i-1} imes hash_u+hash_v)+1) ,但这样错误率很高(很容易有两个形态不同的树hash值相同),可以考虑将式子中的 (1) 以与树的形态有关的值替代,这道题可以使用 ( ext{size(u)})
可以考虑求出 (A, B) 以每个节点为根时的 hash 值,并枚举 (B) 的所有叶子,并快速求出去掉该叶子后整棵树的hash值。按照上面的式子,若以叶子 (u) 为根的hash值为 (x) ,那么去掉 (u) 后整棵树的hash值为 (x-|B|)
我们可以 (O(n)) 求出每个点子树的hash值,记为 (f_i) ,记 (g_i) 为以 (i) 为根的hash值,可以考虑使用二次换根(自顶向下)。
令当前节点为 (v) ,该节点的父亲为 (u) , (g_v) 相当于 (f_v) 多了一个 “以 (u) 为根去掉儿子 (v) 的hash值” 的儿子,暴力枚举 (u) 为根时的所有儿子可以被菊花图卡掉,因此需要化一下式子。可以记录下以每个点为根时的所有儿子的hash值 (val_{u, i}) ,并维护这时的 “hash前缀和” (sum_{u, k}=displaystylesum_{i=0}^kval_{u, i} imes base^{n-i-1}) 。“以 (u) 为根去掉儿子 (v) 的hash值” 就相当于求一个序列删掉一个点后的hash值。令 (n= ext{size(}val_u ext{)}) ,去掉位置 (k) 后的hash值即为 (sum_{u, n}-(sum_{u, k}-sum_{u, k-1}) imes base^{n-k}) 。求出 “以 (u) 为根去掉儿子 (v) 的hash值” 后扫一遍 (v) 的儿子就可以处理出 (g_v, val_v, sum_v) 了。时间复杂度 (O(nlog n))
可以发现hash式子中的最后一项 ( ext{size(u)}) 既满足错误率低,又满足转移方便,因此这是树hash的一个不错的选择(雾
时间复杂度 (O(nlog n))
代码
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long u64;
const int maxn = 1e5 + 10;
const u64 base = 19260817, Inv = 7089841341079321457ull;
int n;
u64 pw[maxn];
struct Tree {
int N, sz[maxn];
u64 dp1[maxn], dp2[maxn];
vector <int> e[maxn];
vector <u64> vec[maxn], sum[maxn];
u64 dfs1(int u, int f) {
sz[u] = 1;
for (int v : e[u]) {
if (v != f) vec[u].push_back(dfs1(v, u)), sz[u] += sz[v];
}
sort(vec[u].begin(), vec[u].end());
int SZ = vec[u].size(); sum[u].resize(SZ);
for (int i = 0; i < SZ; i++) {
sum[u][i] = (i ? base * sum[u][i - 1] : 0) + vec[u][i];
}
return dp1[u] = (SZ ? sum[u].back() : 0) + sz[u];
}
void dfs2(int u, int f) {
int SZ = vec[f].size();
int pos = distance(vec[f].begin(), lower_bound(vec[f].begin(), vec[f].end(), dp1[u]));
vec[u].push_back(sum[f][SZ - 1] - (sum[f][pos] - (pos ? sum[f][pos - 1] : 0)) * pw[SZ - pos - 1] + N - sz[u]);
sort(vec[u].begin(), vec[u].end());
SZ = vec[u].size(), sum[u].resize(SZ);
for (int i = 0; i < SZ; i++) {
sum[u][i] = (i ? base * sum[u][i - 1] : 0) + vec[u][i];
}
dp2[u] = sum[u].back() + N;
for (int v : e[u]) {
if (v != f) dfs2(v, u);
}
}
void build() {
for (int i = 1; i < N; i++) {
int u, v;
scanf("%d %d", &u, &v);
e[u].push_back(v), e[v].push_back(u);
}
dfs1(1, 0);
dp2[1] = dp1[1];
for (int v : e[1]) {
dfs2(v, 1);
}
}
} A, B;
set <u64> S;
int main() {
pw[0] = 1;
for (int i = 1; i < 100005; i++) {
pw[i] = base * pw[i - 1];
}
scanf("%d", &n);
A.N = n, B.N = n + 1;
A.build(), B.build();
for (int i = 1; i <= A.N; i++) {
S.insert(A.dp2[i]);
}
for (int i = 1; i <= B.N; i++) {
if (B.e[i].size() > 1) continue;
u64 x = B.dp2[i] - B.N;
if (S.find(x) != S.end()) {
printf("%d", i); return 0;
}
}
assert(0);
return 0;
}