【题目】
原题地址
给定一棵带边权树,加上一条长度为
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} pi−1之间的边长为 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 < b ) a\Rightarrow b(a<b) a⇒b(a<b),当前二分答案为 m i d mid mid,经过分析,我们可以得到,对于两点 p i , p j ( i < j ) p_i,p_j(i<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+disj−disi≤mid
- 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+∣disi−disa∣+∣disj−disb∣+L≤mid
这两个条件中有至少一个满足。(以下分别称为条件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+L−mid≤disa+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+dj+disj+L−mid≤−disa+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+dj−disj+L−mid≤disa−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+dj−disj+L−mid≤−disa−disb
对 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,di−disi排序,在前者中枚举 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 lim1≤disa+disb
- l i m 2 ≤ − d i s a + d i s b lim_2\leq -dis_a+dis_b lim2≤−disa+disb
- l i m 3 ≤ d i s a − d i s b lim_3\leq dis_a-dis_b lim3≤disa−disb
- l i m 4 ≤ − d i s a − d i s b lim_4\leq -dis_a-dis_b lim4≤−disa−disb
这一步我们从小到大枚举 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;
}
【总结】
思路上不是很难,但是写起来细节很多。