G. Sakurako and Chefir
G. Sakurako and Chefir
Given a tree with vertices rooted at vertex . While walking through it with her cat Chefir, Sakurako got distracted, and Chefir ran away.
To help Sakurako, Kosuke recorded his guesses. In the -th guess, he assumes that Chefir got lost at vertex and had stamina.
Also, for each guess, Kosuke assumes that Chefir could move along the edges an arbitrary number of times:
- from vertex to vertex , if is an ancestor of , the stamina will not change;
- from vertex to vertex , if is not an ancestor of , then Chefir's stamina decreases by .
If Chefir's stamina is , 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 , having stamina.
Vertex is an ancestor of vertex if the shortest path from to the root passes through .
Input
The first line contains a single integer () — the number of test cases.
Each test case is described as follows:
- The first line contains a single integer () — the number of vertices in the tree.
- The next 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 , which denotes the number of guesses made by Kosuke.
- The next lines describe the guesses made by Kosuke, with two integers , .
It is guaranteed that the sum of and the sum of across all test cases does not exceed .
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 having 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 to vertex (after which your stamina will decrease by and become ), and then you can go to vertex ;
- In the second query, from vertex with stamina, you can only reach vertices , , , and ;
- In the third query, from vertex with stamina, you can only reach vertices , , , and ;
解题思路
赛时没留意到 是任意取值从而 可能往上跳出根节点,导致没处理越界情况 WA 麻了。发现后对 与 到根节点距离取最小值就过了,还以为写了个假做法。
定义 表示节点 的深度(规定根节点 )。由于往父节方向点走会消耗 点体力,因此 最多往上走到距离其 的祖先,记为 。由于往下走不消耗体力,因此从 出发能走到子树 (以 为根的子树)中的任意一点,同时这也是从 出发消耗体力不超过 所能到达的所有点。所以距离 最远的点只能在子树 中找。
我们知道树中任意两点 构成路径的长度可以表示成 。设 是子树 中的节点,将 与 构成的路径按最近公共祖先进行分类,那么这些最近公共祖先均是距离 不超过 的祖先。假设 是 的祖先, 是从 再往上走一步的祖先,显然 是 的子节点。 和 的最近公共祖先是 当且仅当 是子树 中的节点且不在子树 中,如下图所示。
定义 表示子树 中到 的最大距离, 表示子树 中到 的次大距离。依次枚举 的祖先 ,那么在子树 且与 构成的最近公共祖先是 的节点中,到 的最大距离就是 。可以考虑倍增优化这个枚举过程。
定义 表示从 往上走 步到达的节点。 表示距离 不超过 的每个祖先 中(不含 )关于 或 的最大值(即对于每个 只考虑在子树 且与 构成的最近公共祖先是 的节点)。状态转移方程就是
其中 是 的父节点。 就是距离 不超过 的祖先所构成的子树中,距离 的最大距离。
最后对于每个询问 和 ,先处理越界问题,即 。记 ,然后枚举 的二进制的每一位,如果第 位是 就往上跳 步,同时将答案与 取最大值,并置 。
AC 代码如下,时间复杂度为 :
#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
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/18502986
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2023-10-25 E. Segment Sum
2022-10-25 E. FTL