洛谷P3761 - [TJOI2017]城市

Portal

Description

给出一个\(n(n\leq5000)\)个点的带边权的树。现在你可以删除一条边并加入一条权值相同的边,使得这\(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\n",ans);
    return 0;
}

P.S.

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

posted @ 2018-04-25 07:50  VisJiao  阅读(228)  评论(0编辑  收藏  举报