原题链接:516D. Drazil and Morning Exercise
题目大意:给定一棵(n)个点的树,定义(f_x=max_{i=1}^n ext{dist}(x,i)),(q)次询问,每次给出一个(l),求树上一个最大的连通块(S),使得(max_{x in S}-min_{x in S}leq l),求最大的连通块大小。
题解:首先,看到(q leq 50),很容易想到这是一个时间复杂度在(O(nq))左右的算法,然后注意到对于每一个点,离它最远的点一定是直径的两端点之一,所以(f_x)可以在(O(n))的时间内求出。接下来考虑对于每一个询问怎么处理。先给出一个算法,然后再解释这个算法为什么是正确的:将所有点按照(f_x)从大到小排序,然后用 two-pointers 从大到小扫,令 two-pointers 的两个端点为(i,j(f_i leq f_j)),第(i)个点把与它相邻的且之前访问过的点并进去(或者说是与它相邻的(f)值更大的点),对于(f_j-l>f_i)把(j)这个点直接删掉。
好了,为什么这么做是正确的呢?我们考虑这棵树如果是以任意一条直径的中点为根,那么对于每一个节点,它的孩子节点的(f)值一定比它大(证明的话,随便画一画应该就可以了),那么每次当前节点被删掉时,以它为根的子树一定被删空了,所以所得的点是一定联通的。
接下来是代码:
#include <cstdio>
#include <algorithm>
using namespace std;
template<typename Elem>
void read(Elem &a){
a=0;
char c=getchar();
while(c<'0'||c>'9'){
c=getchar();
}
while(c>='0'&&c<='9'){
a=(a<<1)+(a<<3)+(c^48);
c=getchar();
}
}
const int Maxn=100000;
typedef long long ll;
int head[Maxn+5],arrive[Maxn<<1|5],val[Maxn<<1|5],nxt[Maxn<<1|5],tot;
void add_edge(int from,int to,int value){
arrive[++tot]=to;
val[tot]=value;
nxt[tot]=head[from];
head[from]=tot;
}
int n,q;
ll f[Maxn+5];
ll dis[Maxn+5];
int root;
int id[Maxn+5];
int fa[Maxn+5],sz[Maxn+5];
bool vis[Maxn+5];
void init_dfs(int u,int fa){
f[u]=max(f[u],dis[u]);
for(int i=head[u];i;i=nxt[i]){
int v=arrive[i];
if(v==fa){
continue;
}
dis[v]=dis[u]+val[i];
init_dfs(v,u);
}
}
bool cmp(int p,int q){
return f[p]>f[q];
}
int find(int x){
if(fa[x]==x){
return x;
}
return fa[x]=find(fa[x]);
}
void merge(int x,int y){
int fa_x=find(x),fa_y=find(y);
if(fa_x==fa_y){
return;
}
fa[fa_y]=fa_x;
sz[fa_x]+=sz[fa_y];
}
int main(){
read(n);
int u,v,w;
for(int i=1;i<n;i++){
read(u),read(v),read(w);
add_edge(u,v,w);
add_edge(v,u,w);
}
dis[1]=0;
root=1;
init_dfs(root,0);
for(int i=1;i<=n;i++){
if(dis[i]>dis[root]){
root=i;
}
}
dis[root]=0;
init_dfs(root,0);
for(int i=1;i<=n;i++){
if(dis[i]>dis[root]){
root=i;
}
}
dis[root]=0;
init_dfs(root,0);
for(int i=1;i<=n;i++){
if(f[i]<f[root]){
root=i;
}
}
for(int i=1;i<=n;i++){
id[i]=i;
}
sort(id+1,id+1+n,cmp);
read(q);
int ans;
ll l;
for(int i=1;i<=q;i++){
ans=1;
read(l);
for(int j=1;j<=n;j++){
fa[j]=j;
sz[j]=1;
vis[j]=0;
}
for(int j=1,k=1;j<=n;j++){
while(k<j&&f[id[k]]-f[id[j]]>l){
sz[find(id[k])]--;
k++;
}
vis[id[j]]=1;
for(int to=head[id[j]];to;to=nxt[to]){
int v=arrive[to];
if(!vis[v]){
continue;
}
merge(id[j],v);
}
ans=max(ans,sz[find(id[j])]);
}
printf("%d
",ans);
}
return 0;
}