题目大意
%
给定一棵有
n
n
n 个点树,树边上有边权,你需要选定两个点,在它们之间添加一条长度为
L
L
L 的边,使得最远的点对最近,输出这个最近的最远点对距离。
%
数据范围
1
⩽
n
⩽
100000
1\leqslant n\leqslant 100000
1⩽n⩽100000
题解
%
考虑最暴力的算法,每次枚举两个点对,并 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+sv−su≤middepu+depv+∣su−sa∣+∣sv−sb∣+L≤mid(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}
⎩⎪⎪⎪⎨⎪⎪⎪⎧−sa−sb≤mid−depu−depv−L−su−sv−sa+sb≤mid−depu−depv−L−su+sv sa−sb≤mid−depu−depv−L+su−sv sa+sb≤mid−depu−depv−L+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
depi−si 和
d
e
p
i
+
s
i
dep_i+s_i
depi+si,每次取出最小的元素来求出最严格的限制,再用线段树维护上面四个不等式的左侧,对于每个
a
a
a,求出满足不等式的区间,这样做的时间复杂度为
Θ
(
n
log
2
2
n
)
Θ(n \log_2^2n)
Θ(nlog22n),期望得分100分。
考虑最后一个过程,如果将
s
s
s 按照
d
e
p
i
−
s
i
dep_i-s_i
depi−si 和
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_2n)
Θ(nlog2n)。
Θ
(
n
log
2
n
)
Θ(n \log_2n )
Θ(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;
}