[SDOI2011]消防

[SDOI2011]消防https://www.luogu.org/problemnew/show/P2491

题目描述

某个国家有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。


首先,暴力肯定炸得不要不要的.
所以科普一下怎么求树的直径(洛谷标签):
先任选一点u作为起点,一遍bfs或dfs求出最长路,找到终点v,再以v为起点求一遍最长路,终点为v',此时的v~v'即是树的直径.
证明(反证法):
设树的直径为s~t.
1.若所选的u是s~t路径上一点,那么找到的v点一定是s或t中一点,否则有dis(u~v)>dis(u~t),此时树的直径应为s~v,与假设矛盾.
2.若u不在s~t路径上:
假设找最长路时搜到了s~t路径上一点X,那么找到的v点一定是s或t中一点(1中已证)
若最长路与直径完全不相交,由于树是连通的,此时找到的最长路u~v上必有一点a与直径s~t连通于点b,则dis(a~v)>dis(a~b)+dis(b~t),此时树的直径应为s~v,矛盾.

dfs

//记得把main函数里面dfs的点vis赋为真
//len和end开全局
void dfs(int now,int Dist)
{
    for(int i=last[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(!vis[to])
        {
            vis[to]=1;
            dist[to]=Dist+edge[i].w;
            if(dist[to]>len)
            {
                len=d[to];
                end=to;
            }
            dfs(to,dist[to]);
        }
    }
}

bfs

int bfs(int s)
{
    int end,len;
    queue<int> que;
    vis[s]=1;
    q.push(s);
    while(!q.empty())
    {
        int now=q.front();
        q.pop();
        for(int i=last[now];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if(!vis[to])
            {
                dist[to]=dist[now]+edge[i].w;
                vis[to]=1;
                q.push(to);
                if(dist[to]>len)
                {
                    len=dist[to];
                    end=to;
                }
            }
        }
    }
    return end;
}

下附正解代码

#include<cstdio>
#include<iostream>
#include<cstring>
#define RG register
using namespace std;
inline int read()
{
    RG int x=0;RG char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x;
}
const int N=300002,INF=1e9;
int n,s,u,v,w,cnt,S,E,L,R,ans=INF,ECC;
int last[N],que[N],dist[N],path[N],d[N],h[N],f[N],g[N];
bool vis[N];
struct edg{
    int to,next,w;
}edge[2*N];
void insert(int u,int v,int w)
{
    edge[++cnt]=(edg){v,last[u],w};last[u]=cnt;
    edge[++cnt]=(edg){u,last[v],w};last[v]=cnt;
}
int bfs(int start,bool op)//start起点,op表示是第几次bfs
{
    for(int i=1;i<=n;i++)que[i]=0,vis[i]=0,dist[i]=0;
    int head=0,tail=1,len=0,end;
    que[1]=start;
    vis[start]=1;
    while(head<tail)
    {
        head++;
        for(int i=last[que[head]];i;i=edge[i].next)
        {
            int v=edge[i].to;
            if(!vis[v])
            {
                vis[v]=1;
                tail++;
                que[tail]=v;
                dist[v]=dist[que[head]]+edge[i].w;
                if(op)path[v]=que[head];//第二遍记录前驱
                if(dist[v]>len)//最长路 即 直径
                {
                    len=dist[v];
                    end=v;
                }
            }
        }
    }
    return end;
}
void Path(int Last)//抠直径
{
    if(Last==S)return;
    d[++cnt]=path[Last];
    Path(d[cnt]);
}
void dfs(int now,int Dist,int x,int left,int right)//计算h不考虑直径上的点,即left,right
{
    for(int i=last[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(!vis[to]&&to!=left&&to!=right)
        {
            vis[to]=1;
            dist[to]=Dist+edge[i].w;
            h[x]=max(h[x],dist[to]);//h[i]维护非直径上的点到d[i]的最大距离
            dfs(to,dist[to],x,left,right);
        }
    }
}
int main()
{
    n=read();s=read();
    for(int i=1;i<n;i++)
    {
        u=read();v=read();w=read();
        insert(u,v,w);
    }
    S=bfs(1,0);
    E=bfs(S,1);
    cnt=1;
    d[cnt]=E;
    Path(E);
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=cnt;i++)vis[d[i]]=1,dfs(d[i],0,i,d[i-1],d[i+1]);
    for(int i=1;i<=cnt;i++)f[i]=max(h[i],f[i-1]+dist[d[i-1]]-dist[d[i]]);//f[i]维护树上d[i]左边的点到d[i]的最大距离(左右根据在直径上的位置区分)
    for(int i=cnt;i>=1;i--)g[i]=max(h[i],g[i+1]+dist[d[i]]-dist[d[i+1]]);//g[i]维护树上d[i]右边的点到d[i]的最大距离(左右根据在直径上的位置区分)
    L=1;R=1;
    while(dist[d[L]]-dist[d[R+1]]<=s&&R<=cnt)R++;//尺取求最小值
    ECC=max(f[L],g[R]);//每次取f[L]与g[R]以及h[L+1~R-1]的最大值
    for(int i=L+1;i<R;i++)ECC=max(ECC,h[i]);
    ans=min(ans,ECC);ECC=0;
    while(R<=cnt&&L<=cnt)
    {
        while(dist[d[L]]-dist[d[R+1]]>s)L++;
        R++;
        ECC=max(f[L],g[R]);
        for(int i=L+1;i<R;i++)ECC=max(ECC,h[i]);
        ans=min(ans,ECC);ECC=0;
    }
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/sdzwyq/p/8423782.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值