• [SDOI2011]消防


    [SDOI2011]消防

    题目来源

    题目描述
    某个国家有n个城市,这n个城市中任意两个都连通且有唯一一条路径,每条连通两个城市的道路的长度为zi(zi<=1000)。
    这个国家的人对火焰有超越宇宙的热情,所以这个国家最兴旺的行业是消防业。由于政府对国民的热情忍无可忍(大量的消防经费开销)可是却又无可奈何总统竞选的国民支持率),所以只能想尽方法提高消防能力。现在这个国家的经费足以在一条边长度和不超过s的路径(两端都是城市建立消防枢纽,为了尽量提高枢纽的利用率,要求其他所有城市到这条路径的距离的最大值最小。
    你受命监管这个项目,你当然需要知道应该把枢纽建立在什么位置上。
    输入输出格式
    输入格式:
    输入包含n行:
    第1行,两个正整数n和s,中间用一个空格隔开。其中n为城市的个数,s为路径长度的上界。设结点编号以此为1,2,……,n。
    从第2行到第n行,每行给出3个用空格隔开的正整数,依次表示每一条边的两个端点编号和长度。例如,“2 4 7”表示连接结点2与4的边的长度为7。
    输出格式:
    输出包含一个非负整数,即所有城市到选择的路径的最大值,当然这个最大值必须是所有方案中最小的。
    【数据规模和约定】
    对于20%的数据,n<=300。
    对于50%的数据,n<=3000。
    对于100%的数据,n<=300000,边长小等于1000。
    

    样例我就不给了

    思路

    因为离树上任意一点最远的点一定是直径的端点,所以我们选择的路径要与直径相交上才会产生最优解,
    至于为什么整条路径都在直径上最优,
    我们不妨假设现在有一棵树,
    将直径上的一个点作为我们要选的路径上的一点,
    我们要以这个点为中心扩展出一条路径,
    离这个点最远的点一定是直径的端点,当前的最大值就是这个长度。
    这时如果我们选的一条边不在直径上,并不会更新这个最大值,
    这样即使不能继续在直径上延长,也对答案没有贡献,
    于是为了方便处理,我们将整条路径都放在直径上,
    但是这些不在直径上的路径的长度不能忽略,需要进行处理,
    想通了这些,这道题就很简单了。

    解法

    首先我们先用两遍bfs求出直径的两个端点,再把整条直径找出来,时间复杂度O(N)
    然后对于每个直径上的点我们做一次dfs,求出不经过直径的以这个点源的路径的最大长度,一起取一个最大值,作为答案的最小值,这个步骤看上去是O(N^2),而实际上我们对于整棵树上的每一个点只会跑一次,于是时间复杂度为O(N)。
    最后我们再以之前求得的最大值为左端点,直径的长度为右端点,二分答案,就可以很轻松的得出结果了。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    const int N=3e5+20;
    int a[N],to[N*2],nex[N*2],w[N*2],b[N],c[N];
    int ll,rr,l,r;
    int bfs(int x){
        memset(c,0,sizeof(c));//两遍bfs求出直径的两个端点
        b[1]=x;
        int head=0,tail=1;
        int ans,s=0;
        while(head!=tail){
            head++;
            for(int i=a[b[head]];i;i=nex[i]){
                if(!c[to[i]]&&to[i]!=x){
                    int y=to[i];
                    tail++;
                    c[y]=c[b[head]]+w[i];
                    b[tail]=y;
                    if(c[y]>s){
                        s=c[y];
                        ans=y;
                    }
                }
            }
        }
        return ans;
    }
    void dfs(int s1,int s2,int x,int t)//对于每个直径上的点dfs一次,求不经过直径的路径最大长度
    {
        l=max(l,t);
        for(int i=a[x];i;i=nex[i])
        {
            if(to[i]!=s1&&to[i]!=s2)
            {
               dfs(x,x,to[i],t+w[i]);
            }
        }
    }
    void _dfs(int fa,int x,int y,int t,int s)//找出直径并对直径路径长度维护一个前缀和,方便以后处理。
    {
        if(x==y)
        {
            b[0]=t;
            b[t]=s;
            r=s;
            return;
        }
        for(int i=a[x];i;i=nex[i])
        {
            if(fa!=to[i])
            {
                _dfs(x,to[i],y,t+1,s+w[i]);
                if(b[0])
                {
                    b[t]=s;
                    dfs(fa,to[i],x,0);
                    return;
                }
            }
        }
    }
    bool pd(int t,int s)//判断答案是否可行。因为答案的左端点就是不经过直径的最大路径长,所以只需再直径上验证即可。
    {
        int i,j;
        for(i=1;i<=b[0];++i)
        {
            if(b[i]>t)
            break;
        }
        i--;
        for(j=i;j<=b[0];++j)
        {
            if(b[j]-b[i]>s)
            break;
        }
        j--;
        return b[b[0]]-b[j]<=t;
    }
    int main()
    {
        int n,s;
        scanf("%d%d",&n,&s);
        for(int i=1,t=0;i<n;++i)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);//存边
            nex[++t]=a[x];to[t]=y;w[t]=z;a[x]=t;
            nex[++t]=a[y];to[t]=x;w[t]=z;a[y]=t;
        }
        ll=bfs(1);
        rr=bfs(ll);
        _dfs(0,ll,rr,1,0);
        while(l!=r)//二分答案
        {
            int mid=(l+r)>>1;
            if(pd(mid,s))r=mid;
            else l=mid+1;
        }
        cout<<l;
        return 0;
    }
    
  • 相关阅读:
    判断&数学&生活
    Tomcat7源码环境搭建
    CentOS 7 下使用虚拟环境Virtualenv安装Tensorflow cpu版记录
    Quartz学习笔记1:Quartz概述
    Docker学习笔记2: Docker 概述
    大数据基础知识问答----spark篇,大数据生态圈
    [MSSQL] [EntityFramework(.Net Core)] 自增长id字段,无法插入数据
    [json-server] RESTful API 中,取主数据时,同时获取多个关联子表的数据
    前后端分离开发之前端自己的API(DB)---- (2)
    前后端分离开发之前端自己的API(DB)---- (1)
  • 原文地址:https://www.cnblogs.com/ljq-despair/p/8669398.html
Copyright © 2020-2023  润新知