Address
Solution
因为边权都是正的,路径一定都会延伸到叶子(无根树中指度数为 1 1 1 的点)。
容易证明,对于任意的 2 y 2y 2y 个叶子,一定存在一种方案使得 y y y 条路径能够覆盖这 2 y 2y 2y 个叶子且路径的并是一个连通块, y y y 条路径能够覆盖的叶子数量的上限也是 2 y 2y 2y。
于是问题变为选出 2 y 2y 2y 个叶子,使得叶子两两之间的路径的并包含的边权和最大。
首先如果树中叶子数量大于等于 2 y 2y 2y,可以直接输出所有边的边权和。
否则因为题目要求包含点 x x x,一个暴力的做法就是以 x x x 为根,贪心选 2 y − 1 2y - 1 2y−1 个叶子。
对此有一个经典的长链剖分的做法, 令一条长链的权值为长链中所有结点连向其父结点的边的边权和,注意到一个结点的子树中如果有叶子被选,那么该结点所在的长链一定被选。
于是我们只要将长链按权值从大到小排序,取前 2 y − 1 2y - 1 2y−1 个即可。
显然数据范围不允许我们枚举根,但可以由调整法证明,包含点 x x x 的最优方案一定至少包含直径的一个端点。
现在只要处理直径的两个端点为根的情况,但此时就不一定能保证包含点 x x x。
若前 2 y − 1 2y - 1 2y−1 个长链不包含点 x x x,我们就需要去掉尽量少并且权值和尽量小的长链来保证包含 x x x。
可以发现,只有以下两种情况:
- 删去第 2 y − 1 2y - 1 2y−1 个长链,并取出 x x x 所在长链,不断向上延伸直到触碰到前 2 y − 2 2y - 2 2y−2 个长链;
- 取出 x x x 所在长链,不断向上延伸直到触碰到前 2 y − 1 2y - 1 2y−1 个长链,把触碰到的长链的下半段改为连向 x x x 的方向。
记录每个长链的排名,倍增计算即可。
时间复杂度 O ( n log n + q log n ) \mathcal O(n \log n + q \log n) O(nlogn+qlogn)。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
char ch;
while (ch = getchar(), !isdigit(ch));
res = ch ^ 48;
while (ch = getchar(), isdigit(ch))
res = res * 10 + ch - 48;
}
template <class T>
inline void put(T x)
{
if (x > 9)
put(x / 10);
putchar(x % 10 + 48);
}
using std::pair;
using std::vector;
const int N = 1e5 + 5;
const int M = 2e5 + 5;
int que[N];
bool vis[N];
int n, q, z, tot, qr, opt;
struct Edge
{
int to, cst; Edge *nxt;
}p[M], *lst[N], *P = p;
inline void Link(int x, int y, int z)
{
(++P)->nxt = lst[x]; lst[x] = P; P->to = y; P->cst = z;
(++P)->nxt = lst[y]; lst[y] = P; P->to = x; P->cst = z;
}
template <class T>
inline T Max(T x, T y) {return x > y ? x : y;}
struct tree
{
pair<int, int> pos[N];
int anc[N][20], dis[N];
int cst[N], pre[N], mx[N], son[N];
int ed[N], _rank[N], id[N], sum[N];
int rt, T;
inline void findRoot(int src)
{
rt = src;
for (int i = 1; i <= n; ++i)
vis[i] = false;
que[qr = 1] = src;
vis[src] = true;
dis[src] = 0;
for (int i = 1, x, y; i <= qr; ++i)
{
x = que[i];
for (Edge *e = lst[x]; e; e = e->nxt)
if (y = e->to, !vis[y])
{
que[++qr] = y;
vis[y] = true;
dis[y] = dis[x] + e->cst;
if (dis[y] > dis[rt])
rt = y;
}
}
}
inline void dfs1(int x)
{
for (int i = 0; anc[x][i]; ++i)
anc[x][i + 1] = anc[anc[x][i]][i];
for (Edge *e = lst[x]; e; e = e->nxt)
{
int y = e->to;
if (y == anc[x][0])
continue ;
anc[y][0] = x;
cst[y] = e->cst;
dis[y] = dis[x] + cst[y];
dfs1(y);
int tmp = mx[y] + cst[y];
if (tmp > mx[x])
mx[x] = tmp, son[x] = y;
}
}
inline void dfs2(int x)
{
if (son[x])
{
id[son[x]] = id[x];
sum[id[x]] += cst[son[x]];
dfs2(son[x]);
}
else
ed[id[x]] = x;
for (Edge *e = lst[x]; e; e = e->nxt)
{
int y = e->to;
if (y == anc[x][0] || y == son[x])
continue ;
id[y] = ++T;
sum[T] = cst[y];
dfs2(y);
}
}
inline void init()
{
dis[rt] = 0;
dfs1(rt);
id[rt] = T = 1;
dfs2(rt);
for (int i = 1; i <= T; ++i)
pos[i] = std::make_pair(sum[i], i);
std::sort(pos + 1, pos + T + 1);
std::reverse(pos + 1, pos + T + 1);
for (int i = 1; i <= T; ++i)
pre[i] = pre[i - 1] + pos[i].first;
for (int i = 1; i <= T; ++i)
_rank[pos[i].second] = i;
}
inline int query(int x, int y)
{
int u = x;
for (int i = 16; i >= 0; --i)
if (anc[u][i] && _rank[id[anc[u][i]]] > y)
u = anc[u][i];
u = anc[u][0];
return Max(pre[y - 1], pre[y] - mx[u]) + dis[ed[id[x]]] - dis[u];
}
inline int calc(int x, int y)
{
return _rank[id[x]] <= y ? pre[y] : query(x, y);
}
}t1, t2;
int main()
{
read(n); read(q); opt = 1;
for (int i = 1, x, y, z; i < n; ++i)
{
read(x); read(y); read(z);
Link(x, y, z);
tot += z;
}
t1.findRoot(1);
t2.findRoot(t1.rt);
t1.init();
t2.init();
int last_ans = 0, x, y;
while (q--)
{
read(x); read(y);
if (opt)
{
x = (x + last_ans - 1) % n + 1;
y = (y + last_ans - 1) % n + 1;
}
y = (y << 1) - 1;
put(last_ans = y >= t1.T ? tot :
Max(t1.calc(x, y), t2.calc(x, y))), putchar('\n');
}
return 0;
}