G. Sakurako and Chefir
Given a tree with $n$ vertices rooted at vertex $1$. While walking through it with her cat Chefir, Sakurako got distracted, and Chefir ran away.
To help Sakurako, Kosuke recorded his $q$ guesses. In the $i$-th guess, he assumes that Chefir got lost at vertex $v_i$ and had $k_i$ stamina.
Also, for each guess, Kosuke assumes that Chefir could move along the edges an arbitrary number of times:
from vertex $a$ to vertex $b$, if $a$ is an ancestor$^{\text{∗}}$ of $b$, the stamina will not change;
from vertex $a$ to vertex $b$, if $a$ is not an ancestor of $b$, then Chefir's stamina decreases by $1$.
If Chefir's stamina is $0$, he cannot make a move of the second type.
For each assumption, your task is to find the distance to the farthest vertex that Chefir could reach from vertex $v_i$, having $k_i$ stamina.
$^{\text{∗}}$Vertex $a$ is an ancestor of vertex $b$ if the shortest path from $b$ to the root passes through $a$.
Input
The first line contains a single integer $t$ ($1\le t\le 10^4$) — the number of test cases.
Each test case is described as follows:
- The first line contains a single integer $n$ ($2 \le n \le 2 \cdot 10^5$) — the number of vertices in the tree.
- The next $n-1$ lines contain the edges of the tree. It is guaranteed that the given edges form a tree.
- The next line consists of a single integer $q$ $(1\le q\le 2 \cdot 10^5)$, which denotes the number of guesses made by Kosuke.
- The next $q$ lines describe the guesses made by Kosuke, with two integers $v_i$, $k_i$ $(1\le v_i \le n, 0 \le k_i\le n)$.
It is guaranteed that the sum of $n$ and the sum of $q$ across all test cases does not exceed $2\cdot 10^5$.
Output
For each test case and for each guess, output the maximum distance to the farthest vertex that Chefir could reach from the starting point $v_i$ having $k_i$ stamina.
Example
Input
3
5
1 2
2 3
3 4
3 5
3
5 1
3 1
2 0
9
8 1
1 7
1 4
7 3
4 9
3 2
1 5
3 6
7
6 0
2 3
6 2
8 2
2 4
9 2
6 3
6
2 1
2 5
2 4
5 6
4 3
3
3 1
1 3
6 5
Output
2 1 2
0 5 2 4 5 5 5
1 3 4
Note
In the first example:
- In the first query, you can go from vertex $5$ to vertex $3$ (after which your stamina will decrease by $1$ and become $0$), and then you can go to vertex $4$;
- In the second query, from vertex $3$ with $1$ stamina, you can only reach vertices $2$, $3$, $4$, and $5$;
- In the third query, from vertex $2$ with $0$ stamina, you can only reach vertices $2$, $3$, $4$, and $5$;
解題思路
賽時沒留意到 $k$ 是任意取值從而 $v$ 可能往上跳出根節點,導致沒處理越界情況 WA 麻了。發現後對 $k$ 與 $v$ 到根節點距離取最小值就過了,還以為寫了個假做法。
定義 $d_v$ 表示節點 $v$ 的深度(規定根節點 $d_1 = 1$)。由於往父節方向點走會消耗 $1$ 點體力,因此 $v$ 最多往上走到距離其 $\min\{k, d_v - 1\}$ 的祖先,記為 $P$。由於往下走不消耗體力,因此從 $v$ 出發能走到子樹 $P$(以 $P$ 為根的子樹)中的任意一點,同時這也是從 $v$ 出發消耗體力不超過 $k$ 所能到達的所有點。所以距離 $v$ 最遠的點只能在子樹 $P$ 中找。
我們知道樹中任意兩點 $u,v$ 構成路徑的長度可以表示成 $d_u + d_v - 2 \cdot \mathrm{lca}(u, v)$。設 $u$ 是子樹 $P$ 中的節點,將 $u$ 與 $v$ 構成的路徑按最近公共祖先進行分類,那麼這些最近公共祖先均是距離 $v$ 不超過 $\min\{k, d_v - 1\}$ 的祖先。假設 $p'$ 是 $v$ 的祖先,$p$ 是 從 $p'$ 再往上走一步的祖先,顯然 $p'$ 是 $p$ 的子節點。$u$ 和 $v$ 的最近公共祖先是 $p$ 當且僅當 $u$ 是子樹 $p$ 中的節點且不在子樹 $p'$ 中,如下圖所示。
定義 $f_1(u)$ 表示子樹 $u$ 中到 $u$ 的最大距離,$f_2(u)$ 表示子樹 $u$ 中到 $u$ 的次大距離。依次列舉 $v$ 的祖先 $p$,那麼在子樹 $p$ 且與 $v$ 構成的最近公共祖先是 $p$ 的節點中,到 $v$ 的最大距離就是 $\displaylines{\begin{cases}f_2(p) + d_v - d_p, &f(p') + 1 = f(p) \\ f_1(p) + d_v - d_p, &f(p') + 1 \ne f(p) \end{cases}}$。可以考慮倍增最佳化這個列舉過程。
定義 $\mathrm{fa}(u,i)$ 表示從 $u$ 往上走 $2^i$ 步到達的節點。$g(u,i)$ 表示距離 $u$ 不超過 $2^i$ 的每個祖先 $p$ 中(不含 $u$)關於 $f_1(p) - d_p$ 或 $f_2(p) - d_p$ 的最大值(即對於每個 $p$ 只考慮在子樹 $p$ 且與 $u$ 構成的最近公共祖先是 $p$ 的節點)。狀態轉移方程就是
\begin{cases}
g(u,i) = \displaylines{\begin{cases}f_2(p_u) - d_{p_u}, &f(u) + 1 = f(p_u) \\ f_1(p_u) - d_{p_u}, &f(u) + 1 \ne f(p_u) \end{cases}} \, , &i=0 \\\\
g(u,i) = \max\{ g(u,i-1), \, g\left(\mathrm{fa}(u,i-1), i-1\right) \} \, , &i > 0
\end{cases}
其中 $p_u = fa(u,0)$ 是 $u$ 的父節點。$g(u,i) + d_v$ 就是距離 $u$ 不超過 $2^i$ 的祖先所構成的子樹中,距離 $v$ 的最大距離。
最後對於每個詢問 $v$ 和 $k$,先處理越界問題,即 $k \gets \min\{ k, d_v - 1 \}$。記 $t = d_v$,然後列舉 $k$ 的二進位制的每一位,如果第 $i$ 位是 $1$ 就往上跳 $2^i$ 步,同時將答案與 $g(v,i) + t$ 取最大值,並置 $v \gets fa(v, i)$。
AC 程式碼如下,時間複雜度為 $O((n + q) \log{n})$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5, M = N * 2;
int h[N], e[M], ne[M], idx;
int d[N], f1[N], f2[N];
int fa[N][18], g[N][18];
void add(int u, int v) {
e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}
void dfs1(int u, int p) {
d[u] = d[p] + 1;
f1[u] = f2[u] = 0;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == p) continue;
dfs1(v, u);
int t = f1[v] + 1;
if (t > f1[u]) f2[u] = f1[u], f1[u] = t;
else if (t > f2[u]) f2[u] = t;
}
}
void dfs2(int u, int p) {
fa[u][0] = p;
if (f1[p] == f1[u] + 1) g[u][0] = f2[p] - d[p];
else g[u][0] = f1[p] - d[p];
for (int i = 1; i <= 17; i++) {
fa[u][i] = fa[fa[u][i - 1]][i - 1];
g[u][i] = max(g[u][i - 1], g[fa[u][i - 1]][i - 1]);
}
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == p) continue;
dfs2(v, u);
}
}
void solve() {
int n, m;
cin >> n;
idx = 0;
memset(h, -1, n + 1 << 2);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
dfs1(1, 0);
dfs2(1, 0);
cin >> m;
while (m--) {
int x, k;
cin >> x >> k;
k = min(k, d[x] - 1);
int ret = f1[x], t = d[x];
for (int i = 0; i <= 17; i++) {
if (k >> i & 1 && x) {
ret = max(ret, g[x][i] + t);
x = fa[x][i];
}
}
cout << ret << ' ';
}
cout << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
參考資料
Codeforces Round 981 (Div. 3) - Codeforces:https://codeforces.com/blog/entry/135421