CF516D Drazil and Morning Exercise

cf

luogu

首先每个点到最远点的距离可以预处理出来,这个距离显然是这个点到树直径两端点的最大值.把那个距离记为\(d_i\),然后从小到大枚举\(d_i\),并强制它为最大的\(d_i\),那么前面\(d_j\)更小的,满足\(d_i-d_j\le L\)的点都可以被计入答案,那么就可以维护一些点的连通情况,支持加点删点,以及维护每个连通块大小,lct即可

考虑发掘其他性质,因为一个点的最远点一定是直径两端点之一,那么把直径上中点作为根,在直径一半边的所有点的最远点都是另一侧的直径端点,并且越往子树走,这个距离会越大.所以如果从大到小枚举最小的\(d_i\),那么当一个满足\(d_j-d_i>L\)的点\(j\)被删掉时,在它子树内的点会被先删掉,所以删它的时候他就是个叶子.那么考虑直接并查集维护,删点是直接给对应并查集大小\(-1\)即可

#include<bits/stdc++.h>
#define LL long long
#define uLL unsigned long long
#define db double

using namespace std;
const int N=1e5+10;
LL rd()
{
	LL x=0,w=1;char ch=0;
	while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*w;
}
int to[N<<1],nt[N<<1],w[N<<1],hd[N],tot=1;
void add(int x,int y,int z)
{
	++tot,to[tot]=y,nt[tot]=hd[x],w[tot]=z,hd[x]=tot;
	++tot,to[tot]=x,nt[tot]=hd[y],w[tot]=z,hd[y]=tot;
}
int n,ff[N],sz[N],rt,sq[N];
int findf(int x){return ff[x]==x?x:ff[x]=findf(ff[x]);}
LL ds[N],mx;
bool cmp(int aa,int bb){return ds[aa]>ds[bb];}
void dfs(int x,int ffa,LL de)
{
	ds[x]=max(ds[x],de);
	for(int i=hd[x];i;i=nt[i])
	{
		int y=to[i];
		if(y==ffa) continue;
		dfs(y,x,de+w[i]);
	}
	if(mx<de) mx=de,rt=x;
}

int main()
{
    ////////QWQWQ
	n=rd();
	for(int i=1;i<n;++i)
	{
		int x=rd(),y=rd(),z=rd();
		add(x,y,z);
	}
	mx=0,dfs(1,0,0);
	int r2=rt;
	mx=0,dfs(r2,0,0);
	dfs(rt,0,0);
	for(int i=1;i<=n;++i) sq[i]=i;
	sort(sq+1,sq+n+1,cmp);
	int q=rd();
	while(q--)
	{
		LL lm=rd();
		for(int i=1;i<=n;++i) ff[i]=i,sz[i]=1;
		int ans=0;
		for(int i=1,j=1;i<=n;++i)
		{
			int x=sq[i];
			while(ds[sq[j]]-ds[x]>lm)
				--sz[findf(sq[j])],++j;
			for(int i=hd[x];i;i=nt[i])
			{
				int y=to[i];
				if(ds[y]>=ds[x]&&findf(y)!=findf(x))
					sz[findf(x)]+=sz[findf(y)],ff[findf(y)]=findf(x);
			}
			ans=max(ans,sz[findf(x)]);
		}
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2019-11-02 17:14  ✡smy✡  阅读(208)  评论(0编辑  收藏  举报