zoukankan      html  css  js  c++  java
  • [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是st路径上一点,那么找到的v点一定是s或t中一点,否则有dis(uv)>dis(ut),此时树的直径应为sv,与假设矛盾.
    2.若u不在s~t路径上:
    假设找最长路时搜到了s~t路径上一点X,那么找到的v点一定是s或t中一点(1中已证)
    若最长路与直径完全不相交,由于树是连通的,此时找到的最长路uv上必有一点a与直径st连通于点b,则dis(av)>dis(ab)+dis(bt),此时树的直径应为sv,矛盾.

    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
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    找工作就上智联,效果真快,然而让我去的公司都是泡我呢
    只能面深度学习岗和算法岗,其他都不会
    2维矩阵前缀和技巧题目
    计算机基础背诵
    集合函数AVG,SUM,MAX,MIN
    集合函数COUNT
    修改删除数据记录
    多表查询
    查询数据表中的记录
    SQL基本语句(3) LOAD DATA INFILE
  • 原文地址:https://www.cnblogs.com/sdzwyq/p/8423782.html
Copyright © 2011-2022 走看看