一种用来合并子树中关于深度的信息的trick
重儿子定义为沿着重儿子走到的叶子深度最深。
一个节点的 $k$ 级祖先所在的重链长度不小于 $k$。
证明显然。
1. 记录重链长度 $len[u]$ 以及链头 $top[u]$。
2. 记录倍增数组 $fa[u][sz]$。
3. 记录每条重链的链头往上跳重链长度的祖先 $up[u][len]$ 以及往下走重链长度的儿子 $down[u][len]$。
4. 记录每一个数字最高位的 $1$ $highbit[n]$。
预处理部分 $O(nlogn)$
先要求 $u$ 的 $k$ 级祖先。$u$ 所在的重链长度不一定大于 $k$,所以无法通过 3 得到的数组 $O(1)$ 得到答案。
可以先用倍增数组往上跳 $2^{highbit[k]}$ 步,得到节点 $v$。
$v$ 为 $u$ 的 $2^{highbit[k]}$级祖先,那么这条重链长度不小于 $2^{highbit[k]}$。
就可以用 3 得到的数组 $O(1)$ 得到答案了。
#include <bits/stdc++.h> const int N = 3e5 + 7; int fa[N][20], n, son[N], len[N], sz[N], dep[N], highbit[N]; int top[N]; std::vector<int> vec[N]; void dfs1(int u, int pre) { sz[u] = dep[u] = dep[pre] + 1; fa[u][0] = pre; for (int i = 1; i < 20; i++) if (fa[u][i - 1]) fa[u][i] = fa[fa[u][i - 1]][i - 1]; else break; for (int v: vec[u]) { if (v == pre) continue; dfs1(v, u); if (sz[v] > sz[son[u]]) son[u] = v, sz[u] = sz[v]; } } void dfs2(int u, int tp) { top[u] = tp; len[u] = sz[u] - dep[tp] + 1; if (!son[u]) return; dfs2(son[u], tp); for (int v: vec[u]) if (v != fa[u][0] && v != son[u]) dfs2(v, v); } std::vector<int> up[N], down[N]; int query(int u, int k) { if (k > dep[u]) return 0; if (!k) return u; u = fa[u][highbit[k]]; k ^= 1 << highbit[k]; if (!k) return u; if (dep[u] - dep[top[u]] == k) return top[u]; if (dep[u] - dep[top[u]] > k) return down[top[u]][dep[u] - dep[top[u]] - k - 1]; return up[top[u]][k - dep[u] + dep[top[u]] - 1]; } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); vec[u].push_back(v); vec[v].push_back(u); } dfs1(1, 0); dfs2(1, 1); for (int i = 1; i <= n; i++) if (i == top[i]) { int l = 0, u = i; while (l++ < len[i] && u) u = fa[u][0], up[i].push_back(u); l = 0, u = i; while (l++ < len[i]) u = son[u], down[i].push_back(u); } for (int i = 1, bit = 1; i <= n; i++) { if ((i >> bit) & 1) bit++; highbit[i] = bit - 1; } int q; scanf("%d", &q); for (int ans = 0; q--; ) { int u, k; scanf("%d%d", &u, &k); u ^= ans, k ^= ans; printf("%d\n", ans = query(u, k)); } return 0; }
在 dsu on tree 里用了两个 $log$ 的写法卡过去了。其实它可以 $O(n)$ 解决。
暴力 dp。$f[u][i]$ 表示 $u$ 的子树中与自己距离为 $i$ 的节点个数。
$f[u][i] = \sum f[v][i - 1]$。这样 dp 是 $O(n^2)$ 的。
但是会发现,第一个和 $u$ 合并的儿子 $v$ 的信息,仅仅只是第二维移动一位。
考虑长链剖分,$O(1)$ 继承重儿子的信息,再和轻儿子暴力合并。
$O(1)$ 继承可以用指针实现。
每个点只会在作为轻儿子的链头时被合并,而且每个点只作为一条重链的轻儿子的链头,而合并的复杂度是 $O(len_v)$
所以总的复杂度是 $O(\sum len)$,也就是 $O(n)$
#include <bits/stdc++.h> const int N = 1e6 + 7; std::vector<int> vec[N]; int dp[N], len[N], son[N], ans[N], n; int *f[N], *cur = dp; void dfs1(int u, int fa) { for (int v: vec[u]) { if (v == fa) continue; dfs1(v, u); if (len[son[u]] < len[v]) son[u] = v; } len[u] = len[son[u]] + 1; } void dfs(int u, int fa) { if (son[u]) { f[son[u]] = f[u] + 1; dfs(son[u], u); ans[u] = ans[son[u]] + 1; } f[u][0] = 1; for (int v: vec[u]) { if (v == fa || v == son[u]) continue; f[v] = cur; cur += len[v]; dfs(v, u); for (int i = 1; i <= len[v]; i++) { f[u][i] += f[v][i - 1]; if (f[u][i] > f[u][ans[u]] || (f[u][i] == f[u][ans[u]] && i < ans[u])) ans[u] = i; } } if (f[u][ans[u]] == 1) ans[u] = 0; } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); vec[u].push_back(v); vec[v].push_back(u); } dfs1(1, 0); f[1] = cur; cur += len[1]; dfs(1, 0); for (int i = 1; i <= n; i++) printf("%d\n", ans[i]); return 0; }
当 $b$ 为 $a$ 的祖先时,答案为 $\sum_{dep[a] - dep[b] \leq k} size[a] - 1$