题目大意: 有一颗 n n n 个点的树,每条边有一个权值(长度),有 m m m 条路线,每条路线的长度为路线上的所有边的长度之和,你可以将任意一条边的长度变成 0 0 0,改完之后,要使所有路径中最长的路径尽可能短,问最短可以是多少。
题解
比较显然的,改的这条边肯定在 m m m 条路线中最长的那条中。
假如现在有两条路径,长度为 1000 1000 1000 和 999 999 999,有一条边长度为 500 500 500,在路径 1 1 1 上,有一条边长度为 2 2 2,同时在路径 1 1 1 和路径 2 2 2 上,你选择将哪一条边的权值改为 0 0 0?
答案很明显——将长度为 2 2 2 的那条边的长度改为 0 0 0。
假如问题变成这样:两条路径,长度 1000 1000 1000 和 500 500 500,一条长度为 500 500 500 的边在路径 1 1 1 上,一条长度为 2 2 2 的边在两条路径上,这个时候你会选谁?
答案也很明显——将长度为 500 500 500 的边变成 0 0 0。
推广一下上面的问题,得到结论:
- 被修改的那条边被长度前 k k k( k > 0 k>0 k>0)的路径覆盖,但是我们不知道 k k k 的值。
于是可以考虑二分,二分最长的边的长度,设当前长度为
m
i
d
mid
mid,如果所有路径的长度都小于等于
m
i
d
mid
mid,那么
r
=
m
i
d
−
1
r=mid-1
r=mid−1,否则判断一下长度大于
m
i
d
mid
mid 的路径是否可以通过 将一条边的长度变成
0
0
0 后长度都变得小于等于
m
i
d
mid
mid,可以的话
r
=
m
i
d
−
1
r=mid-1
r=mid−1 否则
l
=
m
i
d
+
1
l=mid+1
l=mid+1 (……有点啰嗦),伪代码长这个样子:
int ans;
while(l<=r)
{
mid=l+r>>1;
if(所有边的长度都小于mid)r=mid-1,ans=mid;
else
{
if(长度大于mid的边 在 一条边长度变成0 后 长度都小于等于mid)
r=mid-1,ans=mid;
else l=mid+1;
}
}
二分的单调性的证明
设有两个数 x x x 和 y ( x < y ) y(x<y) y(x<y)。
如果长度大于 x x x 的边 在 一条边长度变成 0 0 0 后 长度都小于等于 x x x,那么,同样可以通过删除这条边,使得所有的边的长度都小于 y y y。
证毕
剩下的问题,就是如何判断长度大于 m i d mid mid 的路径,在一条边长度变成 0 0 0 后,是否可以使得这些边长度都小于 m i d mid mid。
显然,我们就是要找出 所有的 被这些路径中的每一条都覆盖到的 边 中的 最长的 那一条。那怎么判断每一条边有没有被所有路径覆盖呢?
对于每一条路径,将路径上的所有边 + 1 +1 +1,最后统计一下所有的边,看看有没有边的值为路径总数,有的话用它的长度更新一下 m a x max max 即可。
显然,“将路径上的所有边+1”这一操作,可以用树上差分搞。
找到最长的那一条边之后,用最长的路径减去这条边的长度,判断是否小于等于 m i d mid mid,如果是的话,那么其他的路径减去这条边也一定小于等于 m i d mid mid,然后 r = m i d − 1 r=mid-1 r=mid−1 即可,因为答案要尽可能小。
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m,len=0;
struct node{int x,y,z,next;};
node e[600010];
int first[300010];
int deep[300010];
int a[300010];
int f[300010][20];
int dis[300010],disfa[300010];//dis用来记录该点到root的距离,disfa记录该点到父亲的距离
void dfs_getfa(int x,int fa)
{
f[x][0]=fa;
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==fa)continue;
deep[y]=deep[x]+1;
dis[y]=dis[x]+e[i].z;
disfa[y]=e[i].z;
dfs_getfa(y,x);
}
}
void buildroad(int x,int y,int z)
{
len++;
e[len].x=x;
e[len].y=y;
e[len].z=z;
e[len].next=first[x];
first[x]=len;
}
int l=0,r=0;
struct nod{int x,y,z;};
nod lu[300010];
void swap(int &x,int &y){int t=x;x=y;y=t;}
int lca(int x,int y)
{
if(deep[x]>deep[y])swap(x,y);
if(deep[x]!=deep[y])
{
int tot=18;
while(deep[x]<deep[y]&&tot>=0)
{
if(deep[f[y][tot]]>=deep[x])y=f[y][tot];
tot--;
}
}
int tot=18;
while(x!=y)
{
while(f[x][tot]==f[y][tot]&&tot>0)tot--;
x=f[x][tot];y=f[y][tot];
}
return x;
}
bool cmp(nod x,nod y){return x.z>y.z;}
int big,mid,d;
void dfs_getans(int x)//统计覆盖情况
{
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==f[x][0])continue;
dfs_getans(y);
a[x]+=a[y];
a[y]=0;//记得初始化回去
}
if(a[x]==d&&disfa[x]>big)big=disfa[x];//如果该边被覆盖次数等于路径数就更新一下big(就是上面说的max)
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<n;i++)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
buildroad(x,y,z);
buildroad(y,x,z);
}
deep[1]=1;dis[1]=0;
dfs_getfa(1,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];
for(int i=1;i<=m;i++)
scanf("%d %d",&lu[i].x,&lu[i].y);
for(int i=1;i<=m;i++)
lu[i].z=dis[lu[i].x]+dis[lu[i].y]-2*dis[lca(lu[i].x,lu[i].y)];//lu[i].z表示该路径的长度,如果不知道为什么可以这样求可以参考代码下面的注释
sort(lu+1,lu+m+1,cmp);//将路径按长度排序
r=lu[1].z;
int ans;
l=max(0,r-1000);//因为最长的边长度不超过1000,所以最后的那条最长的路径最多比现在的最长的路径短1000
//还有,不加这个优化会T掉一个点(各种 卡常+优化 都过不去)
while(l<=r)
{
mid=l+r>>1;
int ll=1,rr=m;d=0;
while(ll<=rr)//二分找到那些长度大于mid的边
{
int mm=ll+rr>>1;
if(lu[mm].z>mid)ll=mm+1,d=mm;
else rr=mm-1;
}
if(d==0)ans=mid,r=mid-1;
else
{
for(int i=1;i<=d;i++)
{
a[lu[i].x]++;
a[lu[i].y]++;
a[lca(lu[i].x,lu[i].y)]-=2;
}
big=0;
dfs_getans(1);
if(lu[1].z-big<=mid)ans=mid,r=mid-1;
else l=mid+1;
}
}
printf("%d",ans);
}