[CF516D] Drazil and Morning Exercise 题解
Description
给定一棵 \(n\) 个点的树,边有边权。
定义 \(f_x = \max_{i=1}^n \text{dist}(x,i)\)。
\(q\) 次询问最大的满足 \(\max_{x \in s} f_x - \min_{x \in s} f_x \le l\) 的连通块 \(s\) 包含的点数。
\(n \le 10^5,q \le 50\)。
Sol
考虑转化问题,变为选出 \(f_i\in [L,R]\),满足 \(R-L\le l\) 的所有点组成的最大连通块。
考虑使用 two pointers 维护,那么如何维护加点和删点后连通块的大小呢?
加点所产生的连通块可以使用并查集维护,但删点就比较麻烦。
仔细思考后我们可以发现一个性质,当前 \(f_i\) 最大的点总在当前树的直径端点上,而直径的端点都在叶子上,所以删去直径的端点并不会将一个连通块变为两个。
所以将 \(f_i\) 从大到小排序,双指针从前往后扫,删点加点时维护一下每个并查集的大小即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
int x = 0, f = 1; char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
void Write(int x) {
if(x < 0) putchar('-'), x = -x;
if(x == 0) putchar('0');
int stk[55], tp = 0;
while(x) stk[++tp] = x % 10, x /= 10;
for(int i = tp; i; i--) putchar(stk[i] + '0');
}
int first[500005], nxt[500005], to[500005], w[500005], tot = 0;
void Add(int x, int y, int z) {nxt[++tot] = first[x]; first[x] = tot; to[tot] = y; w[tot] = z;}
int n, f[100005], id[100005], dis[100005], maxn, mpos, fa[100005], sz[100005], vis[100005];
int findfa(int x) {return fa[x] == x ? x : fa[x] = findfa(fa[x]);}
void dfs(int u, int fa) {
if(dis[u] > maxn) maxn = dis[u], mpos = u;
for(int e = first[u]; e; e = nxt[e]) {
int v = to[e]; if(v == fa) continue;
dis[v] = dis[u] + w[e]; dfs(v, u);
}
}
void dfs2(int u, int fa) {
for(int e = first[u]; e; e = nxt[e]) {
int v = to[e]; if(v == fa) continue;
f[v] = max(f[v], dis[u] + w[e]);
dis[v] = dis[u] + w[e];
dfs2(v, u);
}
}
bool cmp(int A, int B) {
return f[A] > f[B];
}
signed main() {
n = Read();
for(int i = 1; i < n; i++) {
int x = Read(), y = Read(), z = Read();
Add(x, y, z); Add(y, x, z);
}
dfs(1, 0); memset(dis, 0, sizeof(dis));
int A = mpos, B; maxn = 0; dfs(A, 0); B = mpos;
memset(dis, 0, sizeof(dis));
dfs2(A, 0);
memset(dis, 0, sizeof(dis));
dfs2(B, 0);
for(int i = 1; i <= n; i++) id[i] = i;
sort(id + 1, id + n + 1, cmp);
int q = Read();
while(q--) {
memset(sz, 0, sizeof(sz)); int mx = 1;
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i++) fa[i] = i, sz[i] = 1;
int l = 1, r = 1, x = Read();
vis[id[1]] = 1;
while(r + 1 <= n && f[id[l]] - f[id[r + 1]] <= x) {
int u = id[r + 1]; vis[u] = 1;
for(int e = first[u]; e; e = nxt[e]) {
int v = to[e]; if(!vis[v]) continue;
int fv = findfa(v), fu = findfa(u);
fa[fv] = fu; sz[fu] += sz[fv];
mx = max(mx, sz[fu]);
}
++r;
}
while(r != n) {
while(f[id[l]] - f[id[r + 1]] > x) vis[id[l]] = 0, --sz[findfa(id[l])], ++l;
while(r + 1 <= n && f[id[l]] - f[id[r + 1]] <= x) {
int u = id[r + 1]; vis[u] = 1;
for(int e = first[u]; e; e = nxt[e]) {
int v = to[e]; if(!vis[v]) continue;
int fv = findfa(v), fu = findfa(u);
fa[fv] = fu; sz[fu] += sz[fv];
mx = max(mx, sz[fu]);
}
++r;
}
}
cout << mx << endl;
}
return 0;
}