NOIp提高组 2015 运输计划

题目传送门

题目大意: 有一颗 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=mid1,否则判断一下长度大于 m i d mid mid 的路径是否可以通过 将一条边的长度变成 0 0 0 后长度都变得小于等于 m i d mid mid,可以的话 r = m i d − 1 r=mid-1 r=mid1 否则 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=mid1 即可,因为答案要尽可能小。

代码如下:

#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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值