CF516D
给定一棵 n 个点的树,边有边权。
定义一个点的权值为 :该点距离树上所有点的距离中,最大的那个距离
q 次询问最大的满足 所有点的最大权值减最小权值 小于等于 x 的连通块 s 包含的点数。
n ≤ 1e5,q ≤ 50。
====================================================================
首先,离每个点最远的点一定是直径的两个端点之一,我们可以因此求出点权
然后我们发现,点权最小的点,一定是最靠近直径中点的点(如果有中点那么就是中点了)。如果以这个点为根建一棵树,我们就能得到一个随深度递增,权值也递增的树。
那么求连通块时,我们先找一个下边界点,也就是该连通块中点权最大的那个,然后看看往上能到哪个祖先节点,再利用查分的思想给这一段都 + 1 ,最大值就是答案
code
//对不住了,代码有点长,压个行qwq
typedef pair <long long,int> PLI;
vector <PLI> g;
void dfs(int x,int p,int ok){for(int i = head[x];i;i = nxt[i]){int y = to[i];if(y == p) continue;if(ok == 1) dis_a[y] = dis_a[x] + w[i];else dis_b[y] = dis_b[x] + w[i];dfs(y,x,ok);}}
int get(){int j = 0;for(int i = 1;i <= n;i ++)if(!j || dis_a[i] > dis_a[j])j = i;return j;}
int get_average(){int j = 0;for(int i = 1;i <= n;i ++){dis[i] = max(dis_a[i],dis_b[i]);if(!j || dis[i] < dis[j]) j = i;}return j;}
int LOWER_BOUND(long long x)
{
int l = 0;
int r = g.size() - 1;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(g[mid].first < x) l = mid;
else r = mid - 1;
}
return l;
}
void find(int x,int p,long long cnt)
{
g.push_back({dis[x],x});//把当前点插入
PLI res = {dis[x] - cnt,0};
// int fa = lower_bound(g.begin(),g.end(),res) - g.begin() - 1;
int fa = LOWER_BOUND(dis[x] - cnt);
//以 x 点为边界的连通块最小点的值为dis[x] - cnt,因为这棵树dis值小的在上面,所以vector里面存的是dis值递增的一个序列
//一个是手写的,一个是STL,因为pair默认按first排序,所以我们可以用STL
f[g[fa].second] --;
f[x] = 1;
//这里用了差分的思想
for(int i = head[x];i;i = nxt[i])
{
int y = to[i];
if(y == p) continue;
find(y,x,cnt);
f[x] += f[y];
}
ans = max(ans,f[x]);
g.pop_back();
}
int main()
{
cin >> n;
for(int i = 1;i <= n - 1;i ++)
{
int x,y,z;
cin >> x >> y >> z;
add(x,y,z);//加边
add(y,x,z);
}
int u,v;
//下面是求树的直径,因为要求每个点到直径两端的最远距离,
//所以定义了两个数组 dis_a 和 dis_b
// dfs 中的 ok 代表现在求得是哪个数组
dfs(1,-1,1);
u = get();
dis_a[u] = 0;
dfs(u,-1,1);
v = get();
dfs(v,-1,0);
int root = get_average();
//求最靠近直径中点的点,顺便处理出每个点的最远距离
cin >> q;
g.push_back({-INF,0});//要有边界
for(int i = 1;i <= q;i ++)
{
long long x;
cin >> x;
ans = 0;
find(root,-1,x);
cout << ans << endl;
}
return 0;
}