[TJOI2017]城市 树的直径+暴力+优化

Posted tieechal

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[TJOI2017]城市 树的直径+暴力+优化相关的知识,希望对你有一定的参考价值。

Online JudgeLuogu P3761

Label:树的直径,暴力

题目描述

从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作。这个地区一共有n座城市,n-1条高速公路,保证了任意两运城市之间都可以通过高速公路相互可达,但是通过一条高速公路需要收取一定的交通费用。小明对这个地区深入研究后,觉得这个地区的交通费用太贵。

小明想彻底改造这个地区,但是由于上司给他的资源有限,因而小明现在只能对一条高速公路进行改造,改造的方式就是去掉一条高速公路,并且重新修建一条一样的高速公路(即交通费用一样),使得这个地区的两个城市之间的最大交通费用最小(即使得交通费用最大的两座城市之间的交通费用最小),并且保证修建完之后任意两座城市相互可达。如果你是小明,你怎么解决这个问题?

输入

输入数据的第一行为一个整数n,代表城市个数。

接下来的n-1行分别代表了最初的n-1条公路情况。每一行都有三个整数u,v,d。u,v代表这条公路的两端城市标号,d代表这条公路的交通费用。

\\(1 <= u,v <= n\\)\\(1<= d <= 2000\\)

输出

输出数据仅有一行,一个整数,表示进行了最优的改造之后,该地区两城市 之间最大交通费用。

样例

Input

5
1 2 1
2 3 2
3 4 3
4 5 4

Output

7

说明/提示

对于30%的数据,1<=n<500

对于100%的数据,1<=n<=5000

题解

首先说一下这道题最优时间复杂度是\\(O(N)\\)的。

但对于\\(n<=5000\\)的数据,\\(O(N^2)\\)加上一些优化也跑的飞快。

一、最基础的做法

直接暴力\\(O(N)\\)枚举每条边,思考断掉这条边,如何重连使得最大交通费用最小,并且如何去求。

断掉一条边后,在断口处会形成两棵子树。在还没连边之前,这两棵子树的直径都有可能成为答案。接下来如何重连边呢,很明显是将两棵子树的重心相连,随之产生的另一个备选答案就是两棵子树的半径之和。

综上答案为\\(ans=max(直径1,max(直径2,半径1+半径2))\\)

A子树的直径求法:

法1:dfs两遍,同时找到两个端点(下面代码采用)

法2:dfs一遍,维护最长/次长链

B子树的重心、半径求法

需要注意的是,只有确定了重心才能确定半径。

所谓重心是指树的直径上的中点,而半径是到两端点距离较大的一段距离。

所以可以分别以直径上的两个端点为源点跑一遍dfs,处理出每个点到这两点的距离,然后先求重心、再求半径。

求子树的直径是O(N)的,求子树的重心、半径也是O(N)的。所以整个算法的时间复杂度为\\(O(N^2)\\)

二、优化

1.枚举所有边->枚举直径上的边。

2.既然是枚举直径上的边,稍稍观察一下可以知道,两棵子树的某个直径端点就是原直径的一个端点,这样后面就只用dfs一遍即可求出直径了,并且每个点离端点的距离也可以在此时求出,这样后面就不用再多dfs一遍处理每个点离直径端点的距离了,而找重心也只用在直径上找。

上面的优化看似都是常数上的优化,但程序效率却大大提升。。

技术图片

当然如果继续优化可以做到O(N)。
O(N)

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=5010,INF=12345678;
struct edge
    int to,d,nxt;
e[2*N];
int head[N],Ecnt,n;
inline int link(int u,int v,int d)
    e[++Ecnt].to=v,e[Ecnt].d=d,e[Ecnt].nxt=head[u];
    head[u]=Ecnt;

inline int read()
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x;


int id1,id2,ma;

int pa[2],pb[2],dia[2];
int nowx,nowl;
int d[2][N],li[2][N];

void dfs(int x,int fa,int dis,int no,int g)
    if(dis>nowl)nowl=dis,nowx=x;
    d[g][x]=dis;
    li[g][x]=fa;
    
    for(register int i=head[x];i;i=e[i].nxt)
        int y=e[i].to;if(y==fa||y==no)continue;
        dfs(y,x,dis+e[i].d,no,g);
    


inline void go(int root,int ban,int g)
    nowx=nowl=0;    
    dfs(pa[g],0,0,ban,g);
    pb[g]=nowx,dia[g]=nowl;
 

inline int midpoint(int g,int ban)
    int mid=0,mi=INF;
    for(register int i=pb[g];i;i=li[g][i])
        int data=abs(dia[g]-2*d[g][i]);
        if(data<mi)mi=data,mid=i;
    
    return max(d[g][mid],dia[g]-d[g][mid]);

inline int calc(int u,int v,int d)
    go(u,v,0);go(v,u,1);
    int linklen=midpoint(0,v)+midpoint(1,u)+d;
    return max(linklen,max(dia[0],dia[1]));


int nxt[N],nxtlen[N];
void alltree(int x,int fa,int dis)
    if(dis>nowl)nowl=dis,nowx=x;    
    nxt[x]=fa;
    for(register int i=head[x];i;i=e[i].nxt)
        int y=e[i].to;if(y==fa)continue;
        nxtlen[y]=e[i].d;
        alltree(y,x,dis+e[i].d);
    


int main()
    scanf("%d",&n);
    
    register int i;
    for(i=1;i<n;i++)
        int u=read(),v=read(),d=read();
        link(u,v,d),link(v,u,d);
    
    
    int old1,old2;
    alltree(1,0,0);
    old1=nowx;nowx=nowl=0;
    alltree(old1,0,0);
    old2=nowx;nowx=nowl=0;
    pa[1]=old1,pa[0]=old2;
    
    int ans=INF;
    for(i=old2;i;i=nxt[i])
        if(nxt[i]==0)continue;
        ans=min(ans,calc(i,nxt[i],nxtlen[i]));
    
    printf("%d\\n",ans);

以上是关于[TJOI2017]城市 树的直径+暴力+优化的主要内容,如果未能解决你的问题,请参考以下文章

[TJOI2017]城市(树的直径)

2017端午欢乐赛——Day1T3(树的直径+并查集)

9.18 城市道路设计

大臣的旅费---树的直径(dfs)

P5536 XR-3核心城市(树的直径)

P5536 XR-3核心城市(树的直径)