您的位置:首页 > 其它

【BZOJ2282】【Sdoi2011】消防 树的直径+双指针+单调队列 有一系列乱七八糟的证明

2015-03-25 20:57 316 查看

链接:

#include <stdio.h>
int main()
{
    puts("转载请注明出处[vmurder]谢谢");
    puts("网址:blog.csdn.net/vmurder/article/details/44627469");
}


题解:

首先表示这个代码是线性的,是怎么构造数据也卡不住的!

而网上普遍流行的那个二分的是基于直径长度dd的O(dlog2d)O(dlog_2 d)算法,一旦直径长点,然后数据范围大点,它就挂啦!诶我什么心态啊。

算法:

首先答案路径一定在某直径上[证明1,见文末],然后我们求出这个直径序列(任一直径即可[证明2,见文末]),

处理出一些数组:

fif_i表示i是路径左端点时直径上左边的删掉的那段的长度,

gig_i表示i是路径右端点时直径上右边的删掉的那段的长度。

hih_i吧,,每个直径上的点不是都有一棵(或者很多棵)由非此直径上点组成的树(森林)嘛,hih_i表示点ii到这些子节点中最远的那个的距离。

然后在这个序列上跑双指针。就是路径长度不是有限制嘛,然后从左到右枚举左端点,然后右端点是非严格单调右移的。然后时间复杂度线性。而对于一段路径区间[l,r][l,r],它作为枢纽时的答案为

max(max(f[l],g[r]),max{hi,i∈[l,r]})max(max(f[l],g[r]),max{\{h_i,i\in [l,r]}\})

然后最右边那个怎么搞呢~来个单调队列,维护hih_i最大值啊~

实现:

哎算法出来了以后实现就乱七八糟了。

这里觉得这个求树的直径的方法有点污。涨姿势了,我以前写的太渣了!

求树的直径:

求长度

先以11为根跑一遍bfsbfs(防爆栈)求每个点到根距离。

然后再以某最远的点为根再跑一遍之前的bfsbfs函数。

然后再枚举最远点就是直径啦。

求点

然后求出来以后,发现一个端点竟然是根~~

那么bfsbfs时存父亲,然后另一个点一直找父亲就好啦。

正确性?

[证明3,见文末]

代码:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 301000
#define inf 0x3f3f3f3f
using namespace std;
struct Eli
{
    int v,len,next;
}e[N<<1];
int head
,cnt;
inline void add(int u,int v,int len)
{
    e[++cnt].v=v;
    e[cnt].len=len;
    e[cnt].next=head[u];
    head[u]=cnt;
}
int f
,g
,h
,n,m;
int s,t,fa
;
int vis
;
queue<int>q;
inline int bfs(int s,int timecut)
{
    int i,u,v;
    int length=0;
    q.push(s);
    g[s]=0,fa[s]=s,vis[s]=timecut;
    while(!q.empty())
    {
        u=q.front(),q.pop();
        for(i=head[u];i;i=e[i].next)
        {
            v=e[i].v;
            if(vis[v]==timecut)continue;
            g[v]=g[u]+e[i].len;
            vis[v]=timecut;
            fa[v]=u;
            q.push(v);
            length=max(length,g[v]);
        }
    }
    return length;
}
int seq
,temp
;
void work()
{
    int i,j,k;
    int a,b,c;
    int timecut=0;

    scanf("%d%d",&n,&m);
    for(i=1;i<n;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c),add(b,a,c);
    }
    for(s=1,bfs(s,++timecut),i=1;i<=n;i++)
        if(g[i]>g[s])s=i;
    for(bfs(s,++timecut),i=1;i<=n;i++)
        if(g[i]>g[t])t=i;
    seq[n=1]=t;
    timecut++;
    do{t=fa[t],seq[++n]=t,vis[t]=timecut;}while(t!=s);

    for(i=1;i<=n;i++)f[i]=g[seq[1]]-g[seq[i]];
    for(i=1;i<=n;i++)temp[i]=g[seq[i]];
    for(i=1;i<=n;i++)h[i]=bfs(seq[i],timecut);
    for(i=1;i<n;i++)g[i]=temp[i];g
=0;
}
int que
,id
,L,R;
int main()
{
//  freopen("test.in","r",stdin);
    work();
    int l=1,r=0,ans=inf;
    for(l=1;l<=n;l++)
    {
        while(r<n&&f[r+1]-f[l]<=m)
        {
            r++;
            while(L<=R&&que[R]<h[r])R--;
            que[++R]=h[r];
            id[R]=r;
        }
        ans=min(ans,max(max(f[l],g[r]),que[L]));
        if(id[L]<=l)L++;
    }
    printf("%d\n",ans);
    return 0;
}


证明:

证明1:

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

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

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

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

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

证明2:

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

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

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

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

证明3:

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

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

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

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

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

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

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

那么俩子树之间呢?别闹了!深度摆着呢,这个信号点的最大深度点都已经找出来是那个全局最远点了,怎么可能有比它还优的点了呢?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: