CF516D-Drazil and Morning Exercise【树上差分,倍增】

正题

题目链接:https://www.luogu.com.cn/problem/CF516D


题目大意

给出一棵\(n\)个点的树,定义\(f(x)\)表示距离点\(x\)最远的点的距离,\(q\)次询问给出一个\(k\),要求一个最大的连通块满足连通块中所有点的\(f(x)\)最大最小差值不能超过\(k\)

\(1\leq n\leq 10^5,1\leq q\leq 50\)


解题思路

我们找到\(f(x)\)最小的点作为根,那么肯定有每一个点的\(f(x)\)都不小于其父节点的,具体原理是因为每个点出发的最长简单路端点肯定是直径的某一端,而这个最小的\(f(x)\)可以视为直径的中点。

那么对于每个询问我们就直接枚举所有点,然后往上倍增到一个深度最浅的祖先满足\(f(x)-f(z)\leq k\),然后用树上差分给\(z\leftrightarrow x\)路径上所有点的权值\(+1\),最后求一个点权最大的点就好了。

时间复杂度:\(O(qn\log n)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=1e5+10;
struct node{
	ll to,next,w;
}a[N<<1];
ll n,q,tot,rt,ls[N],len[N];
ll f[N][18],dep[N],c[N];
void addl(ll x,ll y,ll w){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;a[tot].w=w;
	return;
}
void dfs(ll x,ll fa){
	for(ll i=ls[x];i;i=a[i].next){
		ll y=a[i].to;
		if(y==fa)continue;dfs(y,x);
		len[x]=max(len[x],len[y]+a[i].w);
	}
	return;
}
void calc(ll x,ll fa,ll mxl){
	ll mx=mxl,mi=0;len[x]=max(len[x],mxl);
	for(ll i=ls[x];i;i=a[i].next){
		ll y=a[i].to;
		if(y==fa)continue;
		if(mx<len[y]+a[i].w)mi=mx,mx=len[y]+a[i].w;
		else if(mi<len[y]+a[i].w)mi=len[y]+a[i].w;
	}
	for(ll i=ls[x];i;i=a[i].next){
		ll y=a[i].to;
		if(y==fa)continue;
		if(mx==len[y]+a[i].w)calc(y,x,mi+a[i].w);
		else calc(y,x,mx+a[i].w);
	}
	return;
}
void sfd(ll x,ll fa){
	dep[x]=dep[fa]+1;
	for(ll i=ls[x];i;i=a[i].next){
		ll y=a[i].to;
		if(y==fa)continue;
		f[y][0]=x;sfd(y,x);
	}
	return;
}
void carc(ll x,ll fa){
	for(ll i=ls[x];i;i=a[i].next){
		ll y=a[i].to;
		if(y==fa)continue;
		carc(y,x);c[x]+=c[y];
	}
	return;
}
void Query(ll d){
	memset(c,0,sizeof(c));len[0]=-1e18;
	for(ll i=1;i<=n;i++){
		ll x=i;
		for(ll j=17;j>=0;j--)
			if(len[i]-len[f[x][j]]<=d)x=f[x][j];
		c[i]++;c[f[x][0]]--;
	}
	carc(rt,0);ll ans=0;
	for(ll i=1;i<=n;i++)ans=max(ans,c[i]);
	printf("%lld\n",ans);return;
}
signed main()
{
	scanf("%lld",&n);
	for(ll i=1,x,y,w;i<n;i++){
		scanf("%lld%lld%lld",&x,&y,&w);
		addl(x,y,w);addl(y,x,w);
	}
	dfs(1,0);
	calc(1,0,0);len[0]=1e18;
	for(ll i=1;i<=n;i++)
		if(len[i]<len[rt])rt=i;
	sfd(rt,0);
	for(int j=1;j<18;j++)
		for(int i=1;i<=n;i++)
			f[i][j]=f[f[i][j-1]][j-1];
	scanf("%lld",&q);
	while(q--){
		ll x;
		scanf("%lld",&x);
		Query(x);
	}
	return 0;
}
posted @ 2022-07-12 12:16  QuantAsk  阅读(30)  评论(0编辑  收藏  举报