[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);
}

原文地址:https://www.cnblogs.com/Tieechal/p/11524682.html

时间: 2024-10-07 11:15:03

[TJOI2017]城市 【树的直径+暴力+优化】的相关文章

HDOJ 题目4123 Bob’s Race(树的直径+RMQ优化)

Bob's Race Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2753    Accepted Submission(s): 888 Problem Description Bob wants to hold a race to encourage people to do sports. He has got trouble

BZOJ 2282 &amp; 树的直径

SDOI2011的Dayx第2题 题意: 在树中找到一条权值和不超过S的链(为什么是链呢,因为题目中提到“使得路径的两端都是城市”,如果不是链那不就不止两端了吗——怎么这么机智的感觉...),使得不在链上的点与这条链的距离最大值最小. SOL: 最大值最小!这不是二分的节奏么?然而hzw学长说二分更直观我却一点都没有体会到... 这道题的关键是猜想(貌似还挺好想)并证明(貌似一直都是可有可无的东西,不过还挺好证的),路径一定在直径上,那么我们先两遍*FS找到直径,用一个队列维护链上的路径,以及预

BZOJ 2657 ZJOI 2012 旅游(journey) 树的直径

题目大意:给出一个凸多边形的三角剖分图,每一个三角形代表一个城市,现在连接这个图中的两个点,问最多能够经过多少个城市. 思路:浙江都是一帮神么.. 这题给的条件简直是不知所云啊..转化十分巧妙.因为每个凸n边形经过三角剖分之后会出现n - 2个三角形,任意一条边只会成为两个城市的公共边或者整个多边形的边.不难推出两个城市的公共边是n - 3条,也就是说把公共边看成是新图的边的话,就会新图就会构成一颗树.之后就是很水的树的直径了... CODE: #include <map> #include

Gym - 100676H Capital City(边强连通分量 + 树的直径)

H. Capital City[ Color: Black ]Bahosain has become the president of Byteland, he is doing his best to make people's liveseasier. Now, he is working on improving road networks between the cities.If two cities are strongly connected, people can use BFS

51 nod 1427 文明 (并查集 + 树的直径)

1427 文明 题目来源: CodeForces 基准时间限制:1.5 秒 空间限制:131072 KB 分值: 160 难度:6级算法题 安德鲁在玩一个叫“文明”的游戏.大妈正在帮助他. 这个游戏里面有n个城市和m条双向的道路.城市从1到n编号.对于每一对城市,他们之间要么有唯一的一条道路,要么就是不可互达.一条道路的定义是一个包含不同城市的序列 v1, v2,...,vk ,  vi  和  vi+1 (1≤ i < k)之间有直接的一条道路相连.这条道路的长度是k-1.两个城市在同一区域的

大臣的旅费(树的直径)

问题描述 很久以前,T王国空前繁荣.为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市. 为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达.同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的. J是T国重要大臣,他巡查于各大城市之间,体察民情.所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情.他有一个钱袋,用于存放往来城市间的路费. 聪明的J发现,如果不在某个城市停下来修整,在连续

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

//前些天的和jdfz的神犇们联考的模拟赛.那天上午大概是没睡醒吧,考场上忘了写输出-1的情况,白丢了25分真是**. 题目描述     小C所在的城市有 n 个供电站,m条电线.相连的供电站会形成一个供电群,那么为了节省材料,供电群是一棵树的形式,也即城市是一个森林的形式(树:V个点,V-1条边的无向连通图,森林:若干棵树).每个供电群中不需要所有供电站都工作,最少只需要一个工作,其余的就都会通过电线收到电,从而完成自己的供电任务.当然,这样会产生延迟.定义两个供电站的延迟为它们之间的电线数量

洛谷P3761:[TJOI2017]城市

题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公路相互可达,但是通过一条高速公路需要收取一定的交通费用.小明对这个地区深入研究后,觉得这个地区的交通费用太贵.小明想彻底改造这个地区,但是由于上司给他的资源有限,因而小明现在只能对一条高速公路进行改造,改造的方式就是去掉一条高速公路,并且重新修建一条一样的高速公路(即交通费用一样),使得这个地区的两个城市之间的最大交通费用最小(即使得交通费用

[xJOI3335] 树的直径(树上最远点)

题目大意就是求解一棵有边权的树上,距离最远的两点间的距离.很容易想到Floyd或是暴力搜索的算法,但是这样过不了…… 介绍一种非常经典的算法:选定任意一个点u作为根节点,从u开始BFS求出距离u最大的点s,再从s点出发BFS到距离s最大的点t,则dis(s,t)即为树的直径 证明: (一):u在直径上:由于u为根节点,所以直径必为u到两子树的最大距离之和,这个证起来很简单. (二):u不在直径上: ① 走着走着走到直径上,那么同(一) ②与直径不相交,这种情况事实上是不存在的,可以用不等式推一下