[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;
}
原文地址:https://www.cnblogs.com/sdzwyq/p/8423782.html