G. Sakurako and Chefir

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 vi and had ki 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 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 vi, having ki stamina.

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 (1t104) — the number of test cases.

Each test case is described as follows:

  • The first line contains a single integer n (2n2105) — the number of vertices in the tree.
  • The next n1 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 (1q2105), which denotes the number of guesses made by Kosuke.
  • The next q lines describe the guesses made by Kosuke, with two integers vi, ki (1vin,0kin).

It is guaranteed that the sum of n and the sum of q across all test cases does not exceed 2105.

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 vi having ki 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 麻了。发现后对 kv 到根节点距离取最小值就过了,还以为写了个假做法。

  定义 dv 表示节点 v 的深度(规定根节点 d1=1)。由于往父节方向点走会消耗 1 点体力,因此 v 最多往上走到距离其 min{k,dv1} 的祖先,记为 P。由于往下走不消耗体力,因此从 v 出发能走到子树 P(以 P 为根的子树)中的任意一点,同时这也是从 v 出发消耗体力不超过 k 所能到达的所有点。所以距离 v 最远的点只能在子树 P 中找。

  我们知道树中任意两点 u,v 构成路径的长度可以表示成 du+dv2lca(u,v)。设 u 是子树 P 中的节点,将 uv 构成的路径按最近公共祖先进行分类,那么这些最近公共祖先均是距离 v 不超过 min{k,dv1} 的祖先。假设 pv 的祖先,p 是从 p 再往上走一步的祖先,显然 pp 的子节点。uv 的最近公共祖先是 p 当且仅当 u 是子树 p 中的节点且不在子树 p 中,如下图所示。

  定义 f1(u) 表示子树 u 中到 u 的最大距离,f2(u) 表示子树 u 中到 u 的次大距离。依次枚举 v 的祖先 p,那么在子树 p 且与 v 构成的最近公共祖先是 p 的节点中,到 v 的最大距离就是 {f2(p)+dvdp,f(p)+1=f(p)f1(p)+dvdp,f(p)+1f(p)。可以考虑倍增优化这个枚举过程。

  定义 fa(u,i) 表示从 u 往上走 2i 步到达的节点。g(u,i) 表示距离 u 不超过 2i 的每个祖先 p 中(不含 u)关于 f1(p)dpf2(p)dp 的最大值(即对于每个 p 只考虑在子树 p 且与 u 构成的最近公共祖先是 p 的节点)。状态转移方程就是

{g(u,i)={f2(pu)dpu,f(u)+1=f(pu)f1(pu)dpu,f(u)+1f(pu),i=0g(u,i)=max{g(u,i1),g(fa(u,i1),i1)},i>0

  其中 pu=fa(u,0)u 的父节点。g(u,i)+dv 就是距离 u 不超过 2i 的祖先所构成的子树中,距离 v 的最大距离。

  最后对于每个询问 vk,先处理越界问题,即 kmin{k,dv1}。记 t=dv,然后枚举 k 的二进制的每一位,如果第 i 位是 1 就往上跳 2i 步,同时将答案与 g(v,i)+t 取最大值,并置 vfa(v,i)

  AC 代码如下,时间复杂度为 O((n+q)logn)

#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

posted @   onlyblues  阅读(65)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2023-10-25 E. Segment Sum
2022-10-25 E. FTL
Web Analytics
点击右上角即可分享
微信分享提示