【Cf #292 D】Drazil and Morning Exercise(树的直径,树上差分)

时间:2023-03-08 18:57:31

【Cf #292 D】Drazil and Morning Exercise(树的直径,树上差分)

有一个经典的问题存在于这个子问题里,就是求出每个点到其他点的最远距离。

这个问题和树的直径有很大的关系,因为事实上距离每个点最远的点一定是直径的两个端点。所以我们可以很容易地进行$3$遍$Dfs$就可以算出这个了,并假设它为$d$。

我们考虑把$d$最小的点设为根,把原树变成一棵有根树,一个重要的结论就是:对于树上每一个节点,它的祖先的$d$一定比它小。

如果我们枚举了$d$最小的点$x$,那可以选择的点都是在$x$这棵子树内的,并且满足$d_{i} - d_{x} <= L$的$i$。

显然直接求解不太行,我们考虑每个点对它祖先的贡献比较合理,对于每个点$x$而言,只有距离它超过$L$的点才不会将$x$的贡献计入,我们可以倍增找到最浅的满足条件的祖先,然后在那里打上差分标记,当递归走出该点时就取消$x$的贡献。

这样我们每个询问就可以$O(nlogn)$做了。

#include <cstdio>
#include <cstring>
#include <algorithm> using namespace std; typedef long long LL;
const int N = , LOG = ; int n, m, rt, ans;
int gr[LOG][N], cnt[N];
LL d[N], L; int yun, las[N], to[N << ], pre[N << ], wi[N << ];
inline void Add(int a, int b, int c) {
to[++yun] = b; wi[yun] = c; pre[yun] = las[a]; las[a] = yun;
} void Dfs(int x, int fat, LL dis) {
d[x] = max(d[x], dis);
for (int i = las[x]; i; i = pre[i]) {
if (to[i] == fat) continue;
Dfs(to[i], x, dis + wi[i]);
}
}
void Dfs_(int x, int fat) {
for (int i = ; i < LOG; ++i) {
if (gr[i - ][x]) gr[i][x] = gr[i - ][gr[i - ][x]];
}
for (int i = las[x]; i; i = pre[i]) {
if (to[i] == fat) continue;
gr[][to[i]] = x;
Dfs_(to[i], x);
}
}
int Solve(int x, int fat) {
int num = , t = x;
for (int i = las[x]; i; i = pre[i]) {
if (to[i] == fat) continue;
num += Solve(to[i], x);
}
num -= cnt[x];
ans = max(ans, num);
for (int i = LOG - ; ~i; --i) {
if (gr[i][t] && d[x] - d[gr[i][t]] <= L) t = gr[i][t];
}
++cnt[gr[][t]];
return num;
} int main() {
scanf("%d", &n);
for (int i = , x, y, z; i < n; ++i) {
scanf("%d%d%d", &x, &y, &z);
Add(x, y, z); Add(y, x, z);
}
Dfs(, , );
rt = max_element(d + , d + + n) - d;
Dfs(rt, , );
rt = max_element(d + , d + + n) - d;
Dfs(rt, , );
rt = min_element(d + , d + + n) - d;
Dfs_(rt, ); scanf("%d", &m);
for (; m; --m) {
scanf("%lld", &L);
memset(cnt, , sizeof cnt);
ans = ;
Solve(rt, );
printf("%d\n", ans);
} return ;
}

$\bigodot$ 技巧&套路:

  • 树上每个点到其他点的最远距离
  • 树上差分的技巧