【二分+双指针】LGP3771[CTSC2017]网络

【题目】
原题地址
给定一棵带边权树,加上一条长度为 L L L的边,使得点对距离中的最大值最小,求这个最小值。

【解题思路】
首先我们一定可以得到一个结论:新建的边一定连接树直径上的两个点,否则往直径移动一定可以更优。
接着,答案满足可二分性。
那么我们现在要做的就是单独将直径取出来考虑如何判定答案是否可行。

现在令直径上有 x x x个点,第 i i i个点为 p i p_i pi p i p_i pi p i − 1 p_{i-1} pi1之间的边长为 l e n i len_{i} leni d i s i = ∑ j = 2 i l e n j dis_{i}=\sum_{j=2}^i len_j disi=j=2ilenj,从 p i p_i pi出发不经过直径的最长路径为 d i d_i di

我们假设最终连边为 a ⇒ b ( a &lt; b ) a\Rightarrow b(a&lt;b) ab(a<b),当前二分答案为 m i d mid mid,经过分析,我们可以得到,对于两点 p i , p j ( i &lt; j ) p_i,p_j(i&lt;j) pi,pj(i<j),满足答案合法性当且仅当:

  • d i + d j + d i s j − d i s i ≤ m i d d_i+d_j+dis_j-dis_i\leq mid di+dj+disjdisimid
  • d i + d j + ∣ d i s i − d i s a ∣ + ∣ d i s j − d i s b ∣ + L ≤ m i d d_i+d_j+|dis_i-dis_a|+|dis_j-dis_b|+L\leq mid di+dj+disidisa+disjdisb+Lmid

这两个条件中有至少一个满足。(以下分别称为条件1和条件2)

那么接下来的做法就变得比较显然了:
我们将条件2绝对值去掉,可以得到四个不带绝对值的限制。

  • d i + d i s i + d j + d i s j + L − m i d ≤ d i s a + d i s b d_i+dis_i+d_j+dis_j+L-mid\leq dis_a+dis_b di+disi+dj+disj+Lmiddisa+disb
  • d i − d i s i + d j + d i s j + L − m i d ≤ − d i s a + d i s b d_i-dis_i+d_j+dis_j+L-mid\leq -dis_a+dis_b didisi+dj+disj+Lmiddisa+disb
  • d i + d i s i + d j − d i s j + L − m i d ≤ d i s a − d i s b d_i+dis_i+d_j-dis_j+L-mid\leq dis_a-dis_b di+disi+djdisj+Lmiddisadisb
  • d i − d i s i + d j − d i s j + L − m i d ≤ − d i s a − d i s b d_i-dis_i+d_j-dis_j+L-mid\leq -dis_a-dis_b didisi+djdisj+Lmiddisadisb

p i p_i pi分别按照 d i + d i s i , d i − d i s i d_i+dis_i,d_i-dis_i di+disi,didisi排序,在前者中枚举 j j j,对应满足条件1的一定是后者的一个前缀,首先忽略这一部分的 ( i , j ) (i,j) (i,j)

对于剩余的 ( i , j ) (i,j) (i,j)我们要求出现过的最紧限制 l i m 1 , l i m 2 , l i m 3 , l i m e 4 lim_1,lim_2,lim_3,lime_4 lim1,lim2,lim3,lime4(分别对应上面四条)。这一步我们可以直接对数组进行排序后取最大的两个。

那么接下来的问题可以转化为判断是否存在 ( a , b ) (a,b) (a,b),满足:

  • l i m 1 ≤ d i s a + d i s b lim_1\leq dis_a+dis_b lim1disa+disb
  • l i m 2 ≤ − d i s a + d i s b lim_2\leq -dis_a+dis_b lim2disa+disb
  • l i m 3 ≤ d i s a − d i s b lim_3\leq dis_a-dis_b lim3disadisb
  • l i m 4 ≤ − d i s a − d i s b lim_4\leq -dis_a-dis_b lim4disadisb

这一步我们从小到大枚举 a a a,那么对应可行的 b b b一定是一段前缀或一段后缀,判断这四个区间是否有交即可。这个区间满足单调性,可以直接双指针扫,当然懒一点上 l o w e r _ b o u n d lower\_bound lower_bound也不会影响复杂度

最后总的复杂度就是 O ( n log ⁡ n log ⁡ S ) O(n\log n \log S) O(nlognlogS),其中 S S S是树的直径。

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e5+10;
const ll INF=(ll)0x3f3f3f3f3f3f3f3f;
int n,cnt,tot,rt,A,B,L;
int head[N],on[N],a[N],b[N],q[N],f[N];
ll tmp,ans,al,ar,bl,br,amx,bmi;
ll dis[N],d[N],dp[N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

struct Tway{int v,w,nex;}e[N<<1];
void add(int u,int v,int w)
{
	e[++tot]=(Tway){v,w,head[u]};head[u]=tot;
	e[++tot]=(Tway){u,w,head[v]};head[v]=tot;
}

bool cmp1(int x,int y){return d[x]+dis[x]<d[y]+dis[y];}
bool cmp2(int x,int y){return d[x]-dis[x]<d[y]-dis[y];}

void dfs(int x,int y,ll z)
{
	//printf("%d %d %lld\n",x,y,z);
	dp[x]=z;f[x]=y;
	if(z>dp[rt]) rt=x;
	for(int i=head[x];i;i=e[i].nex) if(y^e[i].v) dfs(e[i].v,x,z+e[i].w);
}

void dfs2(int x,int y,ll z)
{
	if(z>tmp) tmp=z;
	for(int i=head[x];i;i=e[i].nex) if(y^e[i].v && !on[e[i].v]) dfs2(e[i].v,x,z+e[i].w);
}

void gmax(ll &x,ll y){if(y>x)x=y;}
void gmin(ll &x,ll y){if(y<x)x=y;}

bool check(ll mid)
{
	al=bl=amx=-INF;ar=br=bmi=INF;
	for(int i=1,j=1;i<=cnt;++i)
	{
		while(j<=cnt && d[a[i]]+dis[a[i]]-d[b[j]]+dis[b[j]]>mid)
		{
			gmax(amx,d[b[j]]+dis[b[j]]);gmin(bmi,d[b[j]]-dis[b[j]]);
			++j;
		}
		if(j>1)
		{
			gmax(al,d[a[i]]+dis[a[i]]+amx);gmin(ar,d[a[i]]-dis[a[i]]+bmi);
			gmax(bl,d[a[i]]+dis[a[i]]-bmi);gmin(br,d[a[i]]-dis[a[i]]-amx);
		}
	}
	al+=L-mid;ar+=mid-L;bl+=L-mid;br+=mid-L;
	if(al>ar || bl>br) return 0;
	for(int i=1,j=1,k=1,x=cnt,y=cnt;i<=cnt;++i)
	{
		while(j<=cnt && d[i]-d[j]>=bl) ++j;
		while(k<=cnt && d[i]-d[k]>br) ++k;
		while(x && d[i]+d[x]>=al) --x;
		while(y && d[i]+d[y]>ar) --y;
		if(max(k,x+1)<=min(j-1,y)) return 1;
	}
	return 0;
}

void reset()
{
	tot=cnt=0;
	for(int i=1;i<=n;++i) head[i]=on[i]=0;
}

void init()
{
	for(int i=1;i<n;++i) 
	{
		int u=read(),v=read(),w=read();
		add(u,v,w);
	}
	dfs(rt=1,0,0);dfs(A=rt,0,0);B=rt;
	for(int i=B;i^A;i=f[i]) on[q[++cnt]=i]=1;
	on[q[++cnt]=A]=1;
	//printf("%d %d %d\n",cnt,A,B);
	reverse(q+1,q+cnt+1);
}

void solve()
{
	for(int i=1;i<=cnt;++i)
	{
		tmp=0;
		dfs2(q[i],0,0);
		d[i]=dp[q[i]];dis[i]=tmp;a[i]=b[i]=i;
	}
	sort(a+1,a+cnt+1,cmp1);sort(b+1,b+cnt+1,cmp2);
	ll l=0,r=d[cnt];ans=r;
	while(l<=r)
	{
		ll mid=(l+r)>>1;
		if(check(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%lld\n",ans);
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("LGP3771.in","r",stdin);
	freopen("LGP3771.out","w",stdout);
#endif
	for(;;)
	{
		n=read();L=read(); if(!n && !L) break;
		reset();init();solve();
	}

	return 0;
}

【总结】
思路上不是很难,但是写起来细节很多。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值