[BZOJ1912][Apio2010]patrol 巡逻(树的直径)

Description

这里写图片描述

Input

第一行包含两个整数 n, K(1 ≤ K ≤ 2)。接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n)。

Output

输出一个整数,表示新建了K 条道路后能达到的最小巡逻距离。

Sample Input

8 1

1 2

3 1

3 4

5 3

7 5

8 5

5 6

Sample Output

11

HINT

10%的数据中,n ≤ 1000, K = 1;
30%的数据中,K = 1;
80%的数据中,每个村庄相邻的村庄数不超过 25;
90%的数据中,每个村庄相邻的村庄数不超过 150;
100%的数据中,3 ≤ n ≤ 100,000, 1 ≤ K ≤ 2。

Solution

好像是第一道自己想出的 APIO 。(也不完全是自己想的)
先考虑 K=1 K = 1 的情况:
设选择在 (a,b) ( a , b ) 间连边,那么设 a a b 的 LCA 为 c c
那么走到 c 时,按照 c>a>b>c c − > a − > b − > c 的路线是最优的。
这时候,相对于不新建道路的情况,省去的路程就是:

ab1 a 到 b 的 距 离 − 1

所以,这时候答案显然为:
2×(n1)+1 2 × ( n − 1 ) − 树 的 直 径 长 度 + 1

对于 K=2 K = 2 的情况画个图进行分析,可以发现如果选择加边 (a,b)(c,d) ( a , b ) ( c , d ) ,那么省去的路程就是:( dis(x,y) d i s ( x , y ) 为树上 x x y 的距离)
dis(a,b)+dis(c,d)2×(a,b)(c,d)2 d i s ( a , b ) + d i s ( c , d ) − 2 × 路 径 ( a , b ) 和 路 径 ( c , d ) 的 交 的 长 度 − 2

也就是找出一个四元组 (a,b,c,d) ( a , b , c , d ) 使上式的值最大。
然后乱想了一个 DP ,实现十分恶心,最后发现贪心就行了
考虑在第一次求完直径之后,怎么处理第二次求直径时路径的相交问题。
可以想到在第一次求完直径之后,将直径上的边权都变成 1 − 1 再求一遍直径,
这样两次求得的直径之和,就是 dis(a,b)+dis(c,d)2× d i s ( a , b ) + d i s ( c , d ) − 2 × 交 的 长 度

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Son(u) for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, M = N << 1;
int n, ecnt = 1, nxt[M], adj[N], go[M], val[M], f[N], g[N], f0[N], g0[N];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = 1;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; val[ecnt] = 1;
}
void dfs(int u, int fu) {
    f[u] = g[u] = f0[u] = g0[u] = 0; Son(u) {
        dfs(v, u); if (f[v] + val[e] > f[u]) g[u] = f[u], g0[u] = f0[u],
            f[u] = f[v] + val[e], f0[u] = e;
        else if (f[v] + val[e] > g[u]) g[u] = f[v] + val[e], g0[u] = e;
    }
}
int main() {
    int i, K, x, y, Root = 1; n = read(); K = read();
    For (i, 1, n - 1) x = read(), y = read(), add_edge(x, y);
    dfs(1, 0); For (i, 1, n) if (f[i] + g[i] > f[Root] + g[Root]) Root = i;
    if (K == 1) return printf("%d\n", (n - 1 << 1) - f[Root] - g[Root] + 1), 0;
    x = Root; while (x) val[f0[x]] = val[f0[x] ^ 1] = -1, x = go[f0[x]];
    if (g0[Root]) {
        val[g0[Root]] = val[g0[Root] ^ 1] = -1; x = go[g0[Root]];
        while (x) val[f0[x]] = val[f0[x] ^ 1] = -1, x = go[f0[x]];
    }
    int ans = f[Root] + g[Root], ans1 = 0; dfs(1, 0);
    For (i, 1, n) ans1 = max(ans1, f[i] + g[i]);
    printf("%d\n", (n - 1 << 1) - ans - ans1 + 2); return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值