Luogu P3771 [CTSC2017]网络

题目大意

%   给定一棵有 n n n 个点树,树边上有边权,你需要选定两个点,在它们之间添加一条长度为 L L L 的边,使得最远的点对最近,输出这个最近的最远点对距离。
%   数据范围  1 ⩽ n ⩽ 100000 1\leqslant n\leqslant 100000 1n100000

题解

%   考虑最暴力的算法,每次枚举两个点对,并 dfs 计算一次最远点对的答案。时间复杂度 Θ ( n 3 ) Θ(n^3) Θ(n3)
%   首先加入边的两个端点一定在直径上面,先 dfs 拎出直径来讨论,设直径上共有 t o t tot tot 个点。因而我们只需要考虑找到直径上的一个点对 ⟨ a , b ⟩ \langle a,b\rangle a,b,使得答案尽可能小。直接枚举并 dfs 计算答案的时间复杂度为 Θ ( t o t 2 n ) Θ(tot^2 n) Θ(tot2n)
题目要求的是使得最长的点对之间的路径长度尽量小,考虑二分答案。
设直径上的点 i 到直径起点的距离为 s i s_i si,直径以外的子树内的最长链 g i g_i gi 和最大深度 d e p i dep_i depi,我们尝试以 l = max ⁡ ( i ∈ [ 1 , t o t ] ) ⁡ g i l=\max_{(i∈[1,tot])}⁡g_i l=max(i[1,tot])gi r r r 为原树直径长度作为下界和上界,二分答案 m i d . mid. mid.
%   对于每个 m i d mid mid,我们需要在直径上找一个点对 ⟨ a , b ⟩ \langle a,b\rangle a,b,使得两点间最短距离都小于等于 m i d mid mid,即对于所有 ⟨ u , v ⟩ \langle u,v\rangle u,v 满足
{ d e p u + d e p v + s v − s u ≤ m i d ( 1 ) d e p u + d e p v + ∣ s u − s a ∣ + ∣ s v − s b ∣ + L ≤ m i d ( 2 ) \begin{cases}dep_u+dep_v+s_v-s_u≤mid&(1)\\ dep_u+dep_v+|s_u-s_a |+|s_v-s_b |+L≤mid &(2)\end{cases} {depu+depv+svsumiddepu+depv+susa+svsb+Lmid(1)(2)

%   中存在一个成立,其中 a < b a<b a<b。若 ( 1 ) (1) (1) 式不成立,则 ( 2 ) (2) (2) 式可以拆成四条不等式
{ − s a − s b ≤ m i d − d e p u − d e p v − L − s u − s v ( 3 ) − s a + s b ≤ m i d − d e p u − d e p v − L − s u + s v ( 4 )   s a − s b ≤ m i d − d e p u − d e p v − L + s u − s v ( 5 )   s a + s b ≤ m i d − d e p u − d e p v − L + s u + s v ( 6 ) \begin{cases} -s_a-s_b≤mid-dep_u-dep_v-L-s_u-s_v& &(3)\\ -s_a+s_b≤mid-dep_u-dep_v-L-s_u+s_v& &(4)\\ \ s_a-s_b≤mid-dep_u-dep_v-L+s_u-s_v& &(5)\\ \ s_a+s_b≤mid-dep_u-dep_v-L+s_u+s_v& &(6)\\ \end{cases} sasbmiddepudepvLsusvsa+sbmiddepudepvLsu+sv sasbmiddepudepvL+susv sa+sbmiddepudepvL+su+sv(3)(4)(5)(6)

%   且 ( 2 ) (2) (2) 式成立,当且仅当上面四个不等式均成立,因而我们只需判断上面四个不等式是否有交集即可。
  考虑枚举 a a a,由于上面四个不等式右侧均与 a , b a,b a,b 无关,因而对于上列四条不等式,我们找到最严格的限制条件,然后对于每个 a a a,检查上面四个不等式是否有交集。我们用线段树维护 d e p i − s i dep_i-s_i depisi d e p i + s i dep_i+s_i depi+si,每次取出最小的元素来求出最严格的限制,再用线段树维护上面四个不等式的左侧,对于每个 a a a,求出满足不等式的区间,这样做的时间复杂度为 Θ ( n log ⁡ 2 2 ⁡ n ) Θ(n \log_2^2⁡n) Θ(nlog22n),期望得分100分。
  考虑最后一个过程,如果将 s s s 按照 d e p i − s i dep_i-s_i depisi d e p i + s i dep_i+s_i depi+si 的大小排序,则再求解最严格的限制时,对于 a i a_i ai a i + 1 a_{i+1} ai+1 最严苛的限制 b i b_i bi b i + 1 b_{i+1} bi+1 必然满足在排序数组中的位置单调不上升。
因此,我们可以考虑双指针移动,这样程序的时间复杂度为 Θ ( n log ⁡ 2 ⁡ n ) Θ(n \log_2⁡n) Θ(nlog2n)
   Θ ( n log ⁡ 2 ⁡ n ) Θ(n \log_2⁡n ) Θ(nlog2n) 参考代码:

#include<bits/stdc++.h>
#define ll long long 
#define inf 1e18
using namespace std;
const int maxn=100010;
int n,cnt=1,head[maxn],L,fa[maxn];
int A,B,st[maxn],tot,a[maxn],b[maxn],vis[maxn];
ll dis[maxn],s[maxn],dep[maxn],g[maxn],f[maxn],mn1,mn2,lm1,lm2,lm3,lm4;
struct edge{
	int v,next,w;
}edges[maxn<<1];
char gc(){
	static char*p1,*p2,ch[1000000];
	if(p1==p2)p2=(p1=ch)+fread(ch,1,1000000,stdin);
	return(p1==p2)?EOF:*p1++;
}
void read(int &x){
	x=0;
	char c=gc();
	while(c<'0'||c>'9')c=gc();
	while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=gc();
}
void ins(int u,int v,int w){
	edges[cnt]=(edge){v,head[u],w};head[u]=cnt++;
	edges[cnt]=(edge){u,head[v],w};head[v]=cnt++;
} 
bool cmpa(int x,int y){return dep[x]-s[x]<dep[y]-s[y];}
bool cmpb(int x,int y){return dep[x]+s[x]<dep[y]+s[y];}
//dis[i]:i节点到达初始节点的距离 
//fa[i]:i节点的父亲 
void dfs(int u,int Fa){
	fa[u]=Fa;
	for(int i=head[u];i;i=edges[i].next){
	    int v=edges[i].v;
	    if(v==Fa)continue;
	    dis[v]=dis[u]+edges[i].w; 
	    dfs(v,u);
	}
}
void chkmax(ll&x,ll y){if(x<y)x=y;}
void chkmin(ll&x,ll y){if(x>y)x=y;}
void cal(int u,int Fa){
	g[u]=f[u]=0;
	for(int i=head[u];i;i=edges[i].next){
	    int v=edges[i].v;
	    if(v==Fa||vis[v])continue;
	    cal(v,u);
	    g[u]=max(max(g[u],g[v]),f[u]+f[v]+edges[i].w);
	    //子树内最长的链的长度 
	    f[u]=max(f[u],f[v]+edges[i].w);//到叶子节点的最长距离 
	    //要先更新 g 再更新 f 才能保证 g 的正确性 
	}
}
bool check(ll mid){
	mn1=mn2=lm1=lm2=lm3=lm4=inf;
	for(int i=1,j=tot;i<=tot;++i){
		while(j&&dep[a[i]]-s[a[i]]+dep[b[j]]+s[b[j]]>mid){//满足条件1 
			chkmin(mn1,-dep[b[j]]-s[b[j]]);//找出最严苛的 -d_j-s_j
			chkmin(mn2,-dep[b[j]]+s[b[j]]);//找出最严苛的 -d_j+s_j
			j--;
		}
		ll tmp=mid-dep[a[i]]-L;
		//求出四个条件最严苛的右边 
		chkmin(lm1,tmp-s[a[i]]+mn1);
		chkmin(lm2,tmp+s[a[i]]+mn1);
		chkmin(lm3,tmp-s[a[i]]+mn2);
		chkmin(lm4,tmp+s[a[i]]+mn2);
	}
	int j1=tot+1,j2=1,j3=0,j4=tot;
	for(int i=1;i<=tot;++i){
		while(j1>1&&-s[i]-s[j1-1]<=lm1)j1--;
		//现在[j1,n]都满足(3) 
		
		while(j2<=tot&&s[i]-s[j2]>lm2)j2++;
		//现在[j2,n]都满足(5) 
		
		while(j3<tot&&-s[i]+s[j3+1]<=lm3)j3++;
		//现在[1,j3]都满足(4) 
		 
		while(j4>=1&&s[i]+s[j4]>lm4)j4--;
		//现在[1,j4]都满足(6) 
		
		int l=max(j1,j2),r=min(j3,j4);
		if(l<=r)return true;
	}
	return false;
}
int main(){
    freopen("cross.in","r",stdin);
    freopen("cross.out","w",stdout);
	while(1){
		read(n),read(L);
		if(!n&&!L)break;
		cnt=1;tot=0;
		memset(head,0,sizeof head);
		for(int i=1,u,v,w;i<n;++i)
			read(u),read(v),read(w),ins(u,v,w);
		dis[A=1]=0;
		dfs(1,0);
		for(int i=1;i<=n;++i)
			if(dis[i]>dis[A]) A=i;//找到A:直径的一端 
		dis[B=A]=0;
		dfs(A,0); 
		for(int i=1;i<=n;++i)
			if(dis[i]>dis[B]) B=i;//找到B:直径的另一端
		cal(1,0);
		ll r=g[1],l=0;
		for(int i=B;i;i=fa[i])
			st[++tot]=i,vis[i]=1;
		//st:直径上的所有节点
		//vis:标记直径上的点不再走过 
		reverse(st+1,st+tot+1);//翻转 
		for(int i=1;i<=tot;++i){
			a[i]=b[i]=i;
			cal(st[i],0);//统计直径向下的g和f 
			dep[i]=f[st[i]];//直径上的点向下的最大深度 
			s[i]=dis[st[i]];//到A点的距离 
			l=max(l,g[st[i]]);//求解左端点 
		}
		for(int i=1;i<=tot;++i)vis[st[i]]=0;//消除直径不能走标记 
		sort(a+1,a+tot+1,cmpa);
		sort(b+1,b+tot+1,cmpb); 
		while(l<r){
			ll mid=(l+r)>>1;
			if(check(mid))r=mid;
			else l=mid+1;
		}
		printf("%lld\n",l);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值