[Sdoi2011]消防

Posted cutemush

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Sdoi2011]消防相关的知识,希望对你有一定的参考价值。


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

【样例输入2】
8 6
1 3 2
2 3 2
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3
Sample Output

【样例输出1】
5
【样例输出2】

5
HINT

对于100%的数据,n<=300000,边长小等于1000。

 

/*
证明1:对于直径上的点,离它最远的点一定是直径上的某个点
假设直径为ac
        e
        .
        .
        . 
a.......b.....c(设ab>bc)
        .
		.
		.
		.
		d
对于b点来说,离它最远的应该是a点
如果不是a,而是d,则(c,b,d)应该是直径 
证明2:对于非直径上的点d, 离它最远的点也一定是直径上的某个点
假如离d最远的不是a,而是e.
则db+be>db+ab
于是be>ab,也就是说e离b,比a离b更远,这也开始的证明相违背。
 
 
l...........t.......i.........r
l,r为直径的左右端点,i为我们枚举的一个点,t为另一个点(初值为i的父亲点)
[t,i]的距离不超过规定的Len
则直径上没有被选中的,到选中的边的距离,按要求尽可能小,于是取
ans=min(ans,max(d[t],d[r]-d[i]));
d数组代表每个点到l的距离。 
由于树上所有点,离它们最远的点,一定是直径的两个端点之一,所以一般来说这样求出来后就可以了
但是考虑到给定的Len可能覆盖整个路径。于是求出来的ans为0
于是我们还要将直径上的点一个个拿出来,求出其它不在直径上点到它们的距离
再找出这些距离的最大值, 再与ans取个最大值。 

*/
 

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int fa[310100],vis[301000],last[301000],len=0,d[301000];
struct node
{
    int to,next,w;
}a[601000];
void add(int a1,int a2,int a3)
{
    len++;
    a[len].to=a2;
    a[len].w=a3;
    a[len].next=last[a1];
    last[a1]=len;
}
void dfs(int x)
{
    for(int i=last[x];i;i=a[i].next)
    {
        int to=a[i].to;
        if(vis[to]||fa[x]==to) continue;
        fa[to]=x;
        d[to]=d[x]+a[i].w;
        dfs(to);
    }
}
int main()
{   
    int n,s,x,y,z,ans=2147483647;
    cin>>n>>s;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    int l=1,r=1;
    dfs(l);
    for(int i=1;i<=n;i++)
    if(d[i]>d[l]) 
	    l=i;
    memset(fa,0,sizeof(fa));
    d[l]=0;
	dfs(l);//算出每个点到L的距离,l是直径的左端点 
    for(int i=1;i<=n;i++)
    if(d[i]>d[r]) 
	    r=i;
    int t=r; //找出直径的右端点 
    for(int i=r;i;i=fa[i])//尺取法
    {
        while(fa[t]&&d[i]-d[fa[t]]<=s)
		     t=fa[t];
        ans=min(ans,max(d[t],d[r]-d[i]));
    }
    for(int i=r;i;i=fa[i]) //找出直径上的点 
	    vis[i]=1;
    for(int i=r;i;i=fa[i])
	     d[i]=0,dfs(i);//以直径上每个点为根,找出非直径上的点到它们的距离 
    for(int i=1;i<=n;i++) //在找出这些距离后,求出其最大值 
    if(vis[i]==0)
    ans=max(ans,d[i]);
    cout<<ans;
}

  

以上是关于[Sdoi2011]消防的主要内容,如果未能解决你的问题,请参考以下文章

[Sdoi2011]消防

SDOI2011 第2轮 DAY1消防 [解题报告]

[SDOI2011]消防

BZOJ2282[Sdoi2011]消防 树形DP+双指针法+单调队列

[BZOJ2282] [Sdoi2011]消防

[SDOI2011]消防(单调队列,树的直径,双指针)