• 洛谷P3761


    Portal

    Description

    给出一个(n(nleq5000))个点的带边权的树。现在你可以删除一条边并加入一条权值相同的边,使得这(n)个点仍连通且最长路径最短。求这个最长路径的最小值。

    Solution

    树形DP,树的直径。
    首先我们枚举要删除的边,然后考虑新边加在哪里最好。删除边将原树分成两个连通块(A,B),假设新边为((p,q)),那么最长路径等于(A)中最长路径、(A)中以(p)为端点的最长路径+((p,q))+(B)中以(q)为端点的最长路径、(B)中最长路径中的最大值。由于删边之后(A,B)已经固定,所以只要使(p,q)为其所在连通块中与最远点距离最小的点即可。
    结论:与最远点距离最小的点一定在树的最长路径上,且最远点为最长路径的一个端点。 接下来进行证明。
    设最长路径为((x,y)),与最远点距离最小的点为(M)。若最远点不为最长路径的一个端点,则说明(exists z)使得((M,z)>(M,y))((M,z)>(M,x)),那么由于(M)((x,y))上时((M,x))((M,y))不重合,所以一定可以用((M,z))替换掉其中一个得到一条更长的路径,矛盾。所以若(M)在最长路径上,最远点必然为最长路径的一个端点。
    (M)不在最长路径上,则设(M')((x,y))上距离(M)最近的点,那么一定有((M,x)=(M,M')+(M',x)>(M',x))((M,y)=(M,M')+(M',y)>(M',y))。而由于过(M')的最长路径必为((M',x),(M',y))之一,所以(M')一定比(M)更优。所以与最远点距离最小的点一定在树的最长路径上。
    接下来就好办了,用树形DP分别求出两棵树上的最长路径,并找到尽量平分直径的两个点(p,q),就可以算出删某条边后的最长路径最小值了。

    时间复杂度(O(n^2))

    Code

    //[TJOI2017]城市
    #include <algorithm>
    #include <cstdio>
    using std::min; using std::max;
    inline char gc()
    {
        static char now[1<<16],*s,*t;
        if(s==t) {t=(s=now)+fread(now,1,1<<16,stdin); if(s==t) return EOF;}
        return *s++;
    }
    inline int read()
    {
        int x=0; char ch=gc();
        while(ch<'0'||'9'<ch) ch=gc();
        while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=gc();
        return x;
    }
    int const N=5e3+10;
    int n;
    int cnt,h[N];
    struct edge{int v,w,nxt;} ed[N<<1];
    void edAdd(int u,int v,int w)
    {
        cnt++; ed[cnt].v=v,ed[cnt].w=w,ed[cnt].nxt=h[u],h[u]=cnt;
        cnt++; ed[cnt].v=u,ed[cnt].w=w,ed[cnt].nxt=h[v],h[v]=cnt;
    }
    int fa[N],dFa[N]; int len[N],f[N];
    void dp(int u)
    {
        len[u]=0,f[u]=u;
        for(int i=h[u];i;i=ed[i].nxt)
        {
            int v=ed[i].v,w=ed[i].w;
            if(v==fa[u]) continue;
            fa[v]=u,dFa[v]=w; dp(v);
            if(w+len[v]>len[u]) len[u]=w+len[v],f[u]=f[v];
        }
    }
    struct info
    {
        int len,v1,v2,v3;
        info(int _len=0,int _v1=0,int _v2=0,int _v3=0) {len=_len,v1=_v1,v2=_v2,v3=_v3;}
        int ans()
        {
            int r=2e9;
            for(int u=v1,d=0;u!=fa[v2];d+=dFa[u],u=fa[u]) r=min(r,max(d,len-d));
            for(int u=v3,d=0;u!=fa[v2];d+=dFa[u],u=fa[u]) r=min(r,max(d,len-d));
            return r;
        }
    }d[2];
    void getD(int u,int t)
    {
        int len2=0,f2=u;
        for(int i=h[u];i;i=ed[i].nxt)
        {
            int v=ed[i].v,w=ed[i].w;
            if(v==fa[u]) continue;
            getD(v,t);
            if(w+len[v]>=len2&&f[v]!=f[u]) len2=w+len[v],f2=f[v];
        }
        if(len[u]+len2>=d[t].len) d[t]=info(len[u]+len2,f[u],u,f2);
    }
    int main()
    {
        n=read();
        for(int i=1;i<=n-1;i++)
        {
            int u=read(),v=read(),w=read();
            edAdd(u,v,w);
        }
        int ans=2e9;
        for(int i=1;i<=cnt;i+=2)
        {
            int u=ed[i].v,v=ed[i+1].v;
            fa[u]=v,fa[v]=u; dp(u),dp(v);
            d[0].len=d[1].len=0; getD(u,0),getD(v,1);
            ans=min(ans,max(d[0].ans()+ed[i].w+d[1].ans(),max(d[0].len,d[1].len)));
        }
        printf("%d
    ",ans);
        return 0;
    }
    

    P.S.

    我总感觉这个证明有点循环论证...有错的话请评论哦

  • 相关阅读:
    CMSIS_OS中osMailPut 和 osMessagePut 的问题
    网络:W5500抓包TCP segment of a reassembled PDU
    网络:W5500 UDP数据包格式注意事项
    笔记:把编译时间加入到目标文件
    笔记:git和码云
    笔记:git基本操作
    FreeRtos堆栈检测应用
    一个由自增运算符以及C语法顺序细节引起的bug
    高级文件操作
    linux 权限相关
  • 原文地址:https://www.cnblogs.com/VisJiao/p/LgP3761.html
Copyright © 2020-2023  润新知