换根DP+树的直径【洛谷P3761】 [TJOI2017]城市
P3761 [TJOI2017]城市
题目描述
从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作。这个地区一共有ri座城市,《-1条高速公路,保证了任意两运城市之间都可以通过高速公路相互可达,但是通过一条高速公路需要收取一定的交通费用。小明对这个地区深入研究后,觉得这个地区的交通费用太贵。小明想彻底改造这个地区,但是由于上司给他的资源有限,因而小明现在只能对一条高速公路进行改造,改造的方式就是去掉一条高速公路,并且重新修建一条一样的高速公路(即交通费用一样),使得这个地区的两个城市之间的最大交通费用最小(即使得交通费用最大的两座城市之间的交通费用最小),并且保证修建完之后任意两座城市相互可达。如果你是小明,你怎么解决这个问题?
输入输出格式
输入格式:
输入数据的第一行为一个整数n,代表城市个数。
接下来的n - 1行分别代表了最初的n-1条公路情况。每一行都有三个整数u,v,d。u,v代表这条公路的两端城市标号,d代表这条公路的交通费用。
1 <= u,v <= n,1<= d <= 2000
输出格式:
输出数据仅有一行,一个整数,表示进行了最优的改造之后,该地区两城市 之间最大交通费用。
这道题做了一晚上。
好题。
一句话题意:给你一棵树,让你可以去掉一条边,再在别的位置上加上一条边权相等的边,使得这个图还是一棵树,并且在这棵树的前提下,相距最远的两个点的距离最小。
首先很容易知道我们要换的边一定在原树的直径上,反证法。
所以求出树的直径打上标记,然后枚举拆去哪一条边就行了。
我们拆去一条边之后,原来的树会变成两棵新树,我们可以将这两棵新树任意合并,要求的是合并之后的新树的直径。
可以分成三种情况:首先有可能是两颗小树的直径之一。
第三种情况是当我们把这两棵树的重心连在一起之后,新树的直径:也就是原来两棵子树中以重心为根的前提下的最长链之和加上我们当前拆的这条边的边权。
前两种比较好求,直接还是按照求直径的方法来就行,注意区分两棵子树。
第三种的话,我们需要求出当前树的重心和到重心的最长链。
怎么求?这里用到了换根DP,这也是合并树求直径的常用套路。
首先我们设\(f[u]\)表示以u为根的子树中,到u的最长链,那么枚举子节点,先将当前枚举到的子节点DP出来,然后转移即可。转移方程:\(f(u)=max(f(u),f(v)+edge(i).dis)\)
同时,还需要维护一个次长链,后面有用,跟着f转移就行。
类似点分治求重心的思想,我们可以发现对于u,还有他上面的一条链没有算,这个时候就要进行换根,所以我们的第二遍DP是进行换根,求出以u为根的全局最长链,XJB转移一下就可以了。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
//#define int long long
using namespace std;
const int wx=5017;
inline int read(){
int sum=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=(sum<<1)+(sum<<3)+ch-'0';ch=getchar();}
return sum*f;
}
int totx,toty,num,n,m,pos,tmp,root,maxx,maxy,ans=2147483642;
int head[wx],dis[wx],vis[wx],visx[wx],visy[wx],flag[wx];
int f[wx],size[wx],g[wx],ff[wx],gg[wx];
struct e{
int nxt,to,dis;
}edge[wx*2];
void add(int from,int to,int dis){
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
void dfs_1(int u,int fa){
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa)continue;
dis[v]=dis[u]+edge[i].dis;
dfs_1(v,u);
}
}
void dfs_2(int u,int fa){
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa)continue;
dis[v]=dis[u]+edge[i].dis;
dfs_2(v,u);
}
}
bool dfs_3(int u,int fa){
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa)continue;
if(dfs_3(v,u)||v==pos){
flag[i]=1;
return true;
}
}
return false;
}
void init(){
n=read();
for(int i=1;i<n;i++){
int x,y,z;
x=read(); y=read(); z=read();
add(x,y,z); add(y,x,z);
}
}
void find_length(){
dfs_1(1,0);
for(int i=1;i<=n;i++){
if(dis[i]>tmp){
root=i;
tmp=dis[i];
}
}
memset(dis,0,sizeof dis);
dfs_2(root,0);
tmp=0;
for(int i=1;i<=n;i++){
if(dis[i]>tmp){
pos=i;
tmp=dis[i];
}
}
dfs_3(root,0);//起点root,终点pos
}
void dfs_5(int u,int fa){
visx[u]=1;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa||visy[u])continue;
dis[v]=dis[u]+edge[i].dis;
totx+=edge[i].dis;
dfs_5(v,u);
}
}
void dfs_6(int u,int fa){
visy[u]=1;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa||visx[u])continue;
dis[v]=dis[u]+edge[i].dis;
toty+=edge[i].dis;
dfs_6(v,u);
}
}
void dfs_7(int u){
vis[u]=1;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(!visx[v]||vis[v])continue;
dis[v]=dis[u]+edge[i].dis;
dfs_7(v);
}
}
void dfs_8(int u){
vis[u]=1;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(!visy[v]||vis[v])continue;
dis[v]=dis[u]+edge[i].dis;
dfs_8(v);
}
}
void dp_1(int u,int fa){
vis[u]=1;
f[u]=g[u]=0;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(vis[v]||v==fa)continue;
dp_1(v,u);
if(f[u]<f[v]+edge[i].dis){
g[u]=f[u];
f[u]=f[v]+edge[i].dis;
}
else if(g[u]<f[v]+edge[i].dis){
g[u]=f[v]+edge[i].dis;
}
}
vis[u]=0;
}
int dfs2(int u,int fa){
vis[u]=1;
int re=f[u],w;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(vis[v]||v==fa)continue;
if(f[u]==f[v]+edge[i].dis){
w=edge[i].dis+g[u];
}
else w=edge[i].dis+f[u];
if(w>=f[v]){
g[v]=f[v];f[v]=w;
}
else if(w>g[v]){
g[v]=w;
}
re=min(re,dfs2(v,u));
}
vis[u]=0;
return re;
}
void find_ans(int x,int y,int now){
memset(visx,0,sizeof visx);
memset(visy,0,sizeof visy);
memset(dis,0,sizeof dis);
memset(vis,0,sizeof vis);
// cout<<x<<"zz"<<y<<endl;
int re=0;
dp_1(x,y);re=dfs2(x,y);
dp_1(y,x);re+=dfs2(y,x)+now;
totx=0; toty=0;
dfs_5(x,y); dfs_6(y,x);//将所有点划分到x和y的两个集合内 //接下来去找两棵子树中的重心
int tmpx=0,tmpy=0;
int rootx=0,rooty=0;
for(int i=1;i<=n;i++){
if(visx[i]){
if(tmpx<dis[i]){
tmpx=dis[i];
rootx=i;
}
}
if(visy[i]){
if(tmpy<dis[i]){
tmpy=dis[i];
rooty=i;
}
}
}
memset(dis,0,sizeof dis);
memset(vis,0,sizeof vis);
dfs_7(rootx); dfs_8(rooty);
int zmjx=0,zmjy=0;
for(int i=1;i<=n;i++){
if(visx[i]){
zmjx=max(zmjx,dis[i]);
}
else zmjy=max(zmjy,dis[i]);
}
re=max(re,max(zmjx,zmjy));
ans=min(ans,re);
}
void dfs_4(int u,int fa){
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa||!flag[i])continue;
find_ans(u,v,edge[i].dis);
dfs_4(v,u);
}
}
void work(){
dfs_4(root,0);
printf("%d\n",ans);
}
signed main(){
init();
find_length();
work();
return 0;
}
换根DP+树的直径【洛谷P3761】 [TJOI2017]城市的更多相关文章
- [洛谷P3761] [TJOI2017]城市
洛谷题目链接:[TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速 ...
- P4323-[JSOI2016]独特的树叶【换根dp,树哈希】
正题 题目链接:https://www.luogu.com.cn/problem/P4323 题目大意 给出\(n\)个点的树和加上一个点之后的树(编号打乱). 求多出来的是哪个点(如果有多少个就输出 ...
- [BZOJ4379][POI2015]Modernizacja autostrady[树的直径+换根dp]
题意 给定一棵 \(n\) 个节点的树,可以断掉一条边再连接任意两个点,询问新构成的树的直径的最小和最大值. \(n\leq 5\times 10^5\) . 分析 记断掉一条边之后两棵树的直径为 \ ...
- 洛谷 P4284 [SHOI2014]概率充电器 概率与期望+换根DP
洛谷 P4284 [SHOI2014]概率充电器 概率与期望+换根DP 题目描述 著名的电子产品品牌\(SHOI\) 刚刚发布了引领世界潮流的下一代电子产品-- 概率充电器: "采用全新纳米 ...
- 洛谷$P3647\ [APIO2014]$连珠线 换根$dp$
正解:换根$dp$ 解题报告: 传送门! 谁能想到$9102$年了$gql$居然还没写过换根$dp$呢,,,$/kel$ 考虑固定了从哪个点开始之后,以这个点作为根,蓝线只可能是直上直下的,形如&qu ...
- 模拟赛:树和森林(lct.cpp) (树形DP,换根DP好题)
题面 题解 先解决第一个子问题吧,它才是难点 Subtask_1 我们可以先用一个简单的树形DP处理出每棵树内部的dis和,记为dp0[i], 然后再用一个换根的树形DP处理出每棵树内点 i 到树内每 ...
- Codeforces 891D - Sloth(换根 dp)
Codeforces 题面传送门 & 洛谷题面传送门 换根 dp 好题. 为啥没人做/yiw 首先 \(n\) 为奇数时答案显然为 \(0\),证明显然.接下来我们着重探讨 \(n\) 是偶数 ...
- Codeforces 997D - Cycles in product(换根 dp)
Codeforces 题面传送门 & 洛谷题面传送门 一种换根 dp 的做法. 首先碰到这类题目,我们很明显不能真的把图 \(G\) 建出来,因此我们需要观察一下图 \(G\) 有哪些性质.很 ...
- Acesrc and Travel(2019年杭电多校第八场06+HDU6662+换根dp)
题目链接 传送门 题意 两个绝顶聪明的人在树上玩博弈,规则是轮流选择下一个要到达的点,每达到一个点时,先手和后手分别获得\(a_i,b_i\)(到达这个点时两个人都会获得)的权值,已经经过的点无法再次 ...
随机推荐
- win10/server2019 系统安装 详解
https://www.microsoft.com/zh-cn/software-download/windows10 https://go.microsoft.com/fwlink/?LinkId= ...
- [0day]jQuery Mobile XSS
漏洞影响范围: 任何一个website使用了 jQuery Mobile 并且开放了重定向都有可能存在XSS,并且目前还没有相关补丁信息. 应用介绍: jQuery Mobile是jQuery 框架的 ...
- 2015.3.20 Oracle使用正则表达式
.Oracle正则表达式使用介绍 正则表达式具有强大.便捷.高效的文本处理功能.能够添加.删除.分析.叠加.插入和修整各种类型的文本和数据.Oracle从10g开始支持正则表达式 ..下面通过一些例子 ...
- jhipster初接触
在Windows7部署之前把几个依赖下了 jdk:1.80 Maven :3.3.9 git:2.14.1 npm:唯一要注意的就是配置一个阿里的镜像,不然慢的你崩溃 Yeoman: npm inst ...
- 空中楼阁 ( House )最短路
题目描述: 话说Z4阴差阳错地来到了神秘岛.不久,他们发现,这是一个由n个小岛和一个中心岛组成的群岛,群岛之间有m座桥.令他们感到惊讶的是,这些桥并不是固定不变的,经较长时间的观察,发现它们会随时间作 ...
- css垂直居中方法(二)
第四种方法: 这个方法把一些div的显示方式设置为表格,因此我们可以使用表格的vartial-align属性. 代码如下: <!doctype html> <html lang=&q ...
- android键盘的Done按钮
在EditText中,可以使用setImeOptions()方法来来开启软键盘的"Done"按钮. 示例代码如下:editText.setImeOptions(EditorInfo ...
- ActiveMQ (二) JMS入门
JMS入门 前提:安装好了ActiveMQ ActiveMQ安装 Demo结构: 首先pom.xml引入依赖: <dependency> <groupId>org.apach ...
- 给Activity切换过程添加动画效果
首先,在资源文件中定义一些动画效果 例如: <scale android:duration="@android:integer/config_mediumAnimTime" ...
- __get(),__set(),__isset(),__unset()
__get(),__set(),__isset(),__unset() 在给不可访问属性赋值时,__set()会被调用读取不可访问属性的值时,__get()会被调用 当对不可访问属性调用isset() ...