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]城市 【树的直径+暴力+优化】的更多相关文章

  1. [TJOI2017] 城市 (树的直径,贪心)

    题目链接 Solution 这道题,调了我一晚上... 一直80分 >_<|| ... 考虑到几点: 分开任意一条边 \(u\) ,那么其肯定会断成两棵树. 肯定是分开直径上的边最优,否则 ...

  2. luogu P3761 [TJOI2017]城市 树的直径 bfs

    LINK:城市 谢邀,学弟说的一道毒瘤题. 没有真正的省选题目毒瘤 或者说 写O(n)的做法确实毒瘤. 这里给一个花20min就写完的非常好写的暴力. 容易想到枚举哪条边删掉 删掉之后考虑在哪两个点上 ...

  3. bzoj4890[Tjoi2017]城市(树的半径)

    4890: [Tjoi2017]城市 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 149  Solved: 91[Submit][Status][D ...

  4. [LOJ3014][JOI 2019 Final]独特的城市——树的直径+长链剖分

    题目链接: [JOI 2019 Final]独特的城市 对于每个点,它的答案最大就是与它距离最远的点的距离. 而如果与它距离为$x$的点有大于等于两个,那么与它距离小于等于$x$的点都不会被计入答案. ...

  5. LG5536 「XR-3」核心城市 树的直径

    问题描述 LG5536 题解 两次 \(\mathrm{dfs}\) 求树的直径. 然后找到树的直径的中点. 然后按照 子树中最深的点深度-自己深度 排序,贪心选取前 \(k\) 个. \(\math ...

  6. [NOI2003]逃学的小孩 (贪心+树的直径+暴力枚举)

    Input 第一行是两个整数N(3 <= N <= 200000)和M,分别表示居住点总数和街道总数.以下M行,每行给出一条街道的信息.第i+1行包含整数Ui.Vi.Ti(1<=Ui ...

  7. luogu题解 P1099 【树网的核】树的直径变式+数据结构维护

    题目链接: https://www.luogu.org/problemnew/show/P1099 https://www.lydsy.com/JudgeOnline/problem.php?id=1 ...

  8. [TJOI2017]城市(树的直径)

    [TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公路相互可达, ...

  9. 换根DP+树的直径【洛谷P3761】 [TJOI2017]城市

    P3761 [TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公 ...

随机推荐

  1. RAksmart服务器具备哪些特点?

    美国RAKsmart机房运营多年,前身是提供美国军用服务器业务,拥有着庞大的用户群体和消费者,那RAksmart服务器具备哪些特点呢? 1.美国raksmart服务器特点——硬盘超大 美国raksma ...

  2. Java 集群高可用监控(结合阿里SLB)脚本

    欢迎点评,大家一起来优化 计划思路: 只有在mysql slave java 进程状态都正常的情况下才允许nginx 运行, 否则就干掉它, 负载用的是阿里的SLB #bin/bash #邮件函数  ...

  3. scala中Array简单实用

    /** * 在scala中数组的使用 * 和java很类似,初始化后,长度就固定了,而且元素全部根据其类型初始化 * */ object arrayUse { def main(args: Array ...

  4. 使用treeNMS管理及监控Redis

    Redis做为现在web应用开发的黄金搭担组合,大量的被应用,广泛用于存储session信息,权限信息,交易作业等热数据.做为一名有10年以上JAVA开发经验的程序员,工作中项目也是广泛使用了Redi ...

  5. spring 中常用的设计模式

    一. Spring 中常见的设计模式 工厂模式 : BeanFactory 装饰器模式: BeanWrapper 代理模式: AopProxy 单例模式: ApplicationContext 委派模 ...

  6. USACO training course Mother's Milk /// DFS(有点意思) oj10120

    题目大意: 输入 A B C 为三个容器的容量 一开始A B是空的 C是满的 每一次倾倒只能在 盛的容器满 或 倒的容器空 时才停止 输出当A容器空时 C容器内剩余量的所有可能值 Sample Inp ...

  7. 2019-8-31-dotnet-获取程序所在路径的方法

    title author date CreateTime categories dotnet 获取程序所在路径的方法 lindexi 2019-08-31 16:55:58 +0800 2019-03 ...

  8. 锐速与BBR的原理简单解析

    锐速与BBR的原理简单解析  4 前言 昨天,有一位朋友在我的文章下留言说,锐速和BBR不都是一样,是拥塞算法嘛.因为这方面需要讲的东西比较多,所以我还是专门水一篇文章吧. 锐速 参考资料: http ...

  9. [violet6] 故乡的梦

    题目 描述 不知每日疲于在城市的水泥森林里奔波的你会不会有时也曾向往过乡村的生活.你会不会幻想过,在夏日一个静谧的午后,你沉睡于乡间路边的树荫里,一片叶子落在了你的肩上, 而你正做着一个悠长的梦,一个 ...

  10. 廖雪峰Java12maven基础-2maven进阶-2模块管理

    1. 把大项目拆分为模块是降低软件复杂度的有效方法 在Java项目中,我们通常会会把一个项目分拆为模块,这是为了降低软件复杂度. 例如:我们可以把一个大的项目氛围module-a, module-b, ...