[TJOI2017]城市 【树的直径+暴力+优化】
Online Judge:Luogu 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] 城市 (树的直径,贪心)
题目链接 Solution 这道题,调了我一晚上... 一直80分 >_<|| ... 考虑到几点: 分开任意一条边 \(u\) ,那么其肯定会断成两棵树. 肯定是分开直径上的边最优,否则 ...
- luogu P3761 [TJOI2017]城市 树的直径 bfs
LINK:城市 谢邀,学弟说的一道毒瘤题. 没有真正的省选题目毒瘤 或者说 写O(n)的做法确实毒瘤. 这里给一个花20min就写完的非常好写的暴力. 容易想到枚举哪条边删掉 删掉之后考虑在哪两个点上 ...
- bzoj4890[Tjoi2017]城市(树的半径)
4890: [Tjoi2017]城市 Time Limit: 30 Sec Memory Limit: 128 MBSubmit: 149 Solved: 91[Submit][Status][D ...
- [LOJ3014][JOI 2019 Final]独特的城市——树的直径+长链剖分
题目链接: [JOI 2019 Final]独特的城市 对于每个点,它的答案最大就是与它距离最远的点的距离. 而如果与它距离为$x$的点有大于等于两个,那么与它距离小于等于$x$的点都不会被计入答案. ...
- LG5536 「XR-3」核心城市 树的直径
问题描述 LG5536 题解 两次 \(\mathrm{dfs}\) 求树的直径. 然后找到树的直径的中点. 然后按照 子树中最深的点深度-自己深度 排序,贪心选取前 \(k\) 个. \(\math ...
- [NOI2003]逃学的小孩 (贪心+树的直径+暴力枚举)
Input 第一行是两个整数N(3 <= N <= 200000)和M,分别表示居住点总数和街道总数.以下M行,每行给出一条街道的信息.第i+1行包含整数Ui.Vi.Ti(1<=Ui ...
- luogu题解 P1099 【树网的核】树的直径变式+数据结构维护
题目链接: https://www.luogu.org/problemnew/show/P1099 https://www.lydsy.com/JudgeOnline/problem.php?id=1 ...
- [TJOI2017]城市(树的直径)
[TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公路相互可达, ...
- 换根DP+树的直径【洛谷P3761】 [TJOI2017]城市
P3761 [TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公 ...
随机推荐
- leetcode-17-电话号码的字母组合’
题目描述: 方法一:回溯 class Solution: def letterCombinations(self, digits): """ :type digits: ...
- 0918CSP-S模拟测试赛后总结
14名.110分.可以算是几次大落之后的一次小小的崛起?? 然而sdfz的开挂选手AK了啊…… T2重测前rank7我就高兴地像个傻子??也不看看这次T1是个什么题. 实力还是不行.一眼秒掉了简单题, ...
- 2428: [HAOI2006]均分数据
模拟退火.一种十分玄学的随机算法,网上可以查到比较详细的资料. 先随机地把数分成m组,每次随机地选择一个数,一开始直接选最小的一组,后来就随机一组,把这个数换到该组看看答案能不能变小,如果变小则换,如 ...
- hibernate_03_hibernate一对多的关系映射
1.实体类的一对多的关系映射 一个客户对应多个联系人 Customer.java public class Customer { private Long cust_id; private Strin ...
- Js 数组的各种方法及操作
一.数组去重 var arr = [0,1,20,3,0,45,6,0]; Array.prototype.unrepeat = function(){ var array = []; for(var ...
- idea从github中pull或者push成功之后tomcat启动不了,报Error....
解决方案:删除deployment里面的war包,确定. 再在deployment里面重新添加一个war包,确定,即可.
- STL容器set用法以及codeforces 685B
以前没怎么用过set,然后挂训练赛的时候发现set的妙用,结合网上用法一边学一边写. 首先set是一种容器,可以跟其他STL容器一样用 set<int > s 来定义, 它包含在STL头文 ...
- PAT甲级——A1115 Counting Nodes in a BST【30】
A Binary Search Tree (BST) is recursively defined as a binary tree which has the following propertie ...
- 分享一份Java架构师学习资料,2019年最新整理!
分享一套不错的架构师学习参考资料,免费领取的,无任何套路! 关注Java大后端公众号,在后台回复关键字:大大,即可免费领取,觉得资料不错,转发给其他朋友呗- 长按关注Java大后端公众号领取.
- Python文件操作回顾
with open("D:/Temp/a.txt", mode='w', encoding='utf-8') as f: f.write('hello') with open(&q ...