BZOJ 4890。

在树上断开一条边之后会形成两个联通块,如果要使这一条边接回去之后保持一棵树的形态,那么必须在两个联通块之间各找一个点连接。

那么,对于每一条可能断开的边,它产生的答案是以下两者的最大值:

  1、其中一个联通块的直径

  2、两个联通块中从一个点出发最短的最长距离之和再加上这一条边的权值。

这么拗口,就叫做半径好了。

断开的边是肯定要枚举的,这题的主要矛盾变成了如何求这个半径。

思考一个点的半径会产生于哪里,有以下两种来源:

  1、它子树中的点到它距离的最大值。

  2、除了它的子树中的点,其他所有结点到它父亲结点的距离的最大值加上它到它父亲结点的距离。

其实再细分下去,这个“其他所有结点到它父亲结点的距离的最大值”又有两种来源:

  1、它爷爷的最大值加上它爷爷到它父亲的距离

  2、它父亲到它的某个子树中的点的最大值。

考虑到第二条有可能包含了它,所以搜的时候需要维护三个值:它子树中的点到它距离的最大值$fir_x$,以及它子树中的点到它距离的次大值$sec_x$,以及它去到子树最大值的儿子$maxp_x$。

要注意选择维护$maxp_x$是考虑到含有多条相同链长的情况。

假设$f_{fa}$是父亲到$x$距离的最大值。

我们在往下走的时候,如果$x == maxp_x$那么用$max(f_{fa}, sec_x) + dis(x, son)$更新$f_x$,否则用$max(f_{fa}, fir_x) + dis(x, son)$来更新$f_x$。

每搜到一个点,用$max(fir_x, f_{fa})$更新答案。

其实直径就是$max(fir_x + sec_x)$,搜的时候顺便算一下就好了。

然后还有个优化,就是只有当原来这棵树上的直径的边删掉重构之后才有可能影响答案,因为如果拿出一条直径以外的边重构,直径仍然存在。这样子就可以缩减一些外层循环的计算量。(似乎有整棵树是一条链的极端数据)。

时间复杂度$O(n^2)$,常数迷人。

Code:

#include <cstdio>
#include <cstring>
using namespace std; const int N = ;
const int inf = << ; int n, tot = , head[N], dis[N], maxp[N];
int res, len = , toEdge[N], dia[N], fir[N], sec[N]; struct Edge {
int to, nxt, val;
bool lock;
} e[N << ]; inline void add(int from, int to, int val) {
e[++tot].to = to;
e[tot].val = val;
e[tot].lock = ;
e[tot].nxt = head[from];
head[from] = tot;
} inline void read(int &X) {
X = ; char ch = ; int op = ;
for(; ch > '' || ch < ''; ch = getchar())
if(ch == '-') op = -;
for(; ch >= '' && ch <= ''; ch = getchar())
X = (X << ) + (X << ) + ch - ;
X *= op;
} void dfs(int x, int fat, int nowDis, int inEdge) {
dis[x] = nowDis, toEdge[x] = inEdge;
for(int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if(y == fat) continue;
dfs(y, x, nowDis + e[i].val, i);
}
} inline void chkMax(int &x, int y) {
if(y > x) x = y;
} inline void chkMin(int &x, int y) {
if(y < x) x = y;
} inline int max(int x, int y) {
return x > y ? x : y;
} void dp(int x, int fat) {
for(int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if(y == fat || e[i].lock) continue;
dp(y, x);
if(fir[y] + e[i].val > fir[x])
sec[x] = fir[x], fir[x] = fir[y] + e[i].val, maxp[x] = y;
else if(fir[y] + e[i].val > sec[x])
sec[x] = fir[y] + e[i].val;
}
chkMax(res, fir[x] + sec[x]);
} void dp2(int x, int fat, int f) {
chkMin(res, max(f, fir[x]));
for(int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if(y == fat || e[i].lock) continue;
if(y == maxp[x]) dp2(y, x, max(sec[x] + e[i].val, f + e[i].val));
else dp2(y, x, max(fir[x] + e[i].val, f + e[i].val));
}
} inline int getD(int rt) {
res = ;
dp(rt, );
return res;
} inline int getR(int rt) {
res = inf;
dp2(rt, , );
return res;
} int main() {
// freopen("4.in", "r", stdin); read(n);
for(int x, y, v, i = ; i < n; i++) {
read(x), read(y), read(v);
add(x, y, v), add(y, x, v);
} dfs(, , , );
int root = ; dis[root] = ;
for(int i = ; i <= n; i++)
if(dis[i] > dis[root]) root = i;
dfs(root, , , ); int pnt = ;
for(int i = ; i <= n; i++)
if(dis[i] > dis[pnt]) pnt = i; for(int i = pnt; i != root; i = e[toEdge[i] ^ ].to)
dia[++len] = toEdge[i]; int ans = dis[pnt];
for(int i = ; i <= len; i++) {
int now = ;
e[dia[i]].lock = e[dia[i] ^ ].lock = ; memset(fir, , sizeof(fir));
memset(sec, , sizeof(sec));
memset(maxp, , sizeof(maxp)); chkMax(now, getD(e[dia[i]].to)), chkMax(now, getD(e[dia[i] ^ ].to));
chkMax(now, getR(e[dia[i]].to) + getR(e[dia[i] ^ ].to) + e[dia[i]].val); e[dia[i]].lock = e[dia[i] ^ ].lock = ;
chkMin(ans, now);
} printf("%d\n", ans);
return ;
}

Luogu 3761 [TJOI2017]城市的更多相关文章

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

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

  2. 【BZOJ4890】[TJOI2017]城市(动态规划)

    [BZOJ4890][TJOI2017]城市(动态规划) 题面 BZOJ 洛谷 题解 数据范围都这样了,显然可以暴力枚举断开哪条边. 然后求出两侧直径,暴力在直径上面找到一个点,使得其距离直径两端点的 ...

  3. [洛谷P3761] [TJOI2017]城市

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

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

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

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

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

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

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

  7. BZOJ4890 & 洛谷3761:[TJOI2017]城市——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=4890 https://www.luogu.org/problemnew/show/P3761 从加 ...

  8. [TJOI2017]城市 【树的直径+暴力+优化】

    Online Judge:Luogu P3761 Label:树的直径,暴力 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有n座城市,n-1条高速公路,保证了 ...

  9. Luogu 3758 [TJOI2017]可乐(有向图邻接矩阵幂的意义 矩阵快速幂)

    题目描述 加里敦星球的人们特别喜欢喝可乐.因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的1号城市上.这个可乐机器人有三种行为: 停在原地,去下一个相邻的城市,自爆.它每一秒都会随机 ...

随机推荐

  1. 剑指offer--20.矩形覆盖

    链接:https://www.nowcoder.com/questionTerminal/72a5a919508a4251859fb2cfb987a0e6来源:牛客网 @DanielLea 思路分析: ...

  2. linux shell 学习笔记--内部命令学习

    .基本命令 新手必须要掌握的初级命令 ls 基本的列出所有文件的命令.但是往往就是因为这个命令太简单,所以我们总是低估它.比如 ,用 -R 选项,这是递归选项,ls 将会以目录树的形式列出所有文件, ...

  3. centos 下配置oracle11gR2开机自启

    这里使用的环境是 CentOS 6.6 ,并且已经装好了oracle11gR2 oracle启动分为两个步骤: 1.启动监听 2.启动服务 1.root 用户下修改ORATAB(将N该为Y): [ro ...

  4. jsp中引入JavaScript的方法

    1:在页面中直接嵌入JavaScript <script language="javascript">..........</script> 2:链接外部J ...

  5. 【转载】BusyBox 简化嵌入式 Linux 系统

    原文网址:http://www.ibm.com/developerworks/cn/linux/l-busybox/ BusyBox 是很多标准 Linux® 工具的一个单个可执行实现.BusyBox ...

  6. 用IntelliJ IDEA建maven项目,如何解决main下无法创建java源文件

    一.缺少java源文件 如下图: 现在想要在main下面增加java源文件,效果图如下所示: 首先,在main目录下新建java文件夹,就是源文件目录, 然后,右键,选择Mark Directory ...

  7. [置顶] if语句的陷阱

    if else if是只要有满足条件的,就不再对之后的else if进行判断 #include<stdio.h> void main() { char a=0; if(a==0) { a= ...

  8. socket模型

    Socket: "主机" + "端口" = 套接字/插座; 仅仅是一个通信模型,不属于七层协议(网络协议). 一台电脑(IP)的一个应用程序(端口) 和 另一台 ...

  9. 【转】link_to中delete无效的问题解决

    问题一 1.问题描述:点击[删除]链接之后,跳到了show页面,完全没有弹出框提示 <%= link_to "删除", product_path(product.id), : ...

  10. [转]在 Windows 操作系统中的已知安全标识符(Sid security identifiers)

    安全标识符 (SID) 是用于标识安全主体或安全组在 Windows 操作系统中的可变长度的唯一值.常用 Sid 的 Sid 标识普通用户的一组或通用组.跨所有操作系统,它们的值保持不变. 此信息可用 ...