[Sdoi2011]消防

 

/*
很棒的思路:树的直径+二分+bfs 
ans=max{ans,min{所有点到“消防枢纽”左端点的距离, 所有点到“消防枢纽”右端点的距离, 所有点到“消防枢纽”路径上某点的距离}} 
显然我们知道 “消防枢纽”一定在树的直径上(感性认识一下就好)
然后,我们套用黄学长的思路:
答案一定不会小于所有点到直径的距离最大值,只要把直径上的边权设为0,任选直径上一点bfs可得
将最大值(其他点的)作为二分下界,直径作为上界 
二分直径左右端点的舍弃部分{判断条件:当前答案下[l,r]分别到左、右端点的dis<=len,看[l,r]的dis是否<=s} 
*/
#include<cstdio>
#include<iostream>
using namespace std;
const int N=3e5+5;
struct edge{int v,w,next;}e[N<<1];int tot,head[N];
int n,s,top,st[N],fa[N],q[N],dis[N];bool mark[N];
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void add(int x,int y,int z){
    e[++tot].v=y;e[tot].w=z;e[tot].next=head[x];head[x]=tot;
    e[++tot].v=x;e[tot].w=z;e[tot].next=head[y];head[y]=tot;
}
inline void bfs(int S){
    for(int i=1;i<=n;i++) dis[i]=-1;
    unsigned short h=0,t=1;q[t]=S;dis[S]=0;
    while(h!=t){
        int x=q[++h];
        for(int i=head[x];i;i=e[i].next){
            if(dis[e[i].v]==-1){
                if(mark[e[i].v])
                    dis[e[i].v]=dis[x];
                else
                    dis[e[i].v]=dis[x]+e[i].w;
                fa[e[i].v]=x;
                q[++t]=e[i].v;
            }
        }
    }
}
inline bool judge(int len){
    int l=1,r=top;
    while(st[1]-st[l+1]<=len&&l<top) l++;
    while(st[r-1]<=len&&r>1) r--;
    return st[l]-st[r]<=s;
}
int main(){
    freopen("mindist.in","r",stdin);
    freopen("mindist.ans","w",stdout);
    n=read();s=read();
    for(int i=1,x,y,z;i<n;i++) x=read(),y=read(),z=read(),add(x,y,z);
    int rt=1,rw=0;
    bfs(rt);
    for(int i=1;i<=n;i++) if(dis[i]>dis[rt]) rt=i;
    bfs(rt);
    for(int i=1;i<=n;i++) if(dis[i]>dis[rw]) rw=i;
    int D=dis[rw];
    st[++top]=dis[rw];mark[rw]=1;
    for(;rw!=rt;mark[rw=fa[rw]]=1) st[++top]=dis[fa[rw]];
    int l=0,r=D;
    bfs(rt);
    for(int i=1;i<=n;i++) l=max(l,dis[i]);
    if(s<D){
        while(l<r){
            int mid=l+r>>1;
            if(judge(mid)) r=mid;
            else l=mid+1;
        }
    }
    printf("%d\n",l);
    return 0;
}

 

证明1:

  证题:为什么它在树的某条直径上?

  我们可以采用反证法。如果有一边它的最后一段不在任何一条直径上: 

  如果整条路径与直径没有交集,那么可以从其中一点走到某条直径上,然后任意方向往直径一端走,发现在直径上走的路程肯定比从那点到该路径然后走较长一边的路径长,因为直径上另一边的那一端没有选择选择路径那一部分作为直径的另一半。 

  如果有交集,那么同理,此路径与选定直径会在一个点岔开,然后它岔开了,就表示选的路径短了,那么选择直径,这一头最远距离是这个路径的那一段,而选择那个不是很优的路径,则这一头最远距离是岔开后直径的那一段。显然选择直径更优,且对于其它不合法路径有同样结论,因为你选择任意一条不合法路径最远距离都是与直径岔开后直径的那一段。

  然后我们证了一头,另一头同理(什么同理啊,把上面的话粘一遍就好了)。

证明2:

  证题:为什么随便找个直径就行?为什么有多个直径时可行?

  如果有多条直径且相交,那么交点+直径们 一定是一个菊花形图形,然后每条链都相同,然后你选定那个点就行了,因为无论怎么选路径答案都是那个损样儿。

  如果直径们部分重合,那么我们选择中间的重合部分就可以得到最优答案,且不会有更优,那么任意一条直径都包含它,所以随便选个就行,最后一定会处理到这段重合部分。

  如果两直径没有任何交集,那么一定有一条路径把它们相连,然后路径的两个点把两直径分成两部分,那么我们各取较长的部分,再加上链接它们的路径,就一定比它们更长了。所以他们根本不是直径,或者说与命题相悖,不存在这种情况balabala。

证明3:

  证题:为什么那么两遍bfs就可以搞出树的一条直径?

  首先第一次bfs以某点x为根,然后我们搜出了一个子树中最远的点。 

  显然如果直径经过x,那么一定是不在一棵子树中的最远点和次远点,一定可以求出最远距离。

  而如果在最远点子树内的路径,一定是这棵子树内最远的路径。证明思想跟[证明1]差不多。 

  我们发现要到的另一个点一定是个叶子节点。那么我们可以考虑从最远点发出一个信号,一路向根走,取每个点的其它子树中的最远点,然后取得最值。这个过程bfs可以完美而不遗漏地实现,但是它为什么是对的呢?

  如果在其它子树中自然存在一个更长距离,那么信号点到全局最远点的距离肯定比里面三点重心到那两点中任意一点的距离长,毕竟深度在那摆着呢,那么肯定可以替换掉一部分,即取其中一个点到全局最远点,然后这样一定比两点之间路径长。 

  所以其它子树中不存在内部的更长距离。 

  那么俩子树之间呢?别闹了!深度摆着呢,这个信号点的最大深度点都已经找出来是那个全局最远点了,怎么可能有比它还优的点了呢?

posted @ 2017-03-09 15:59  神犇(shenben)  阅读(188)  评论(0编辑  收藏  举报