【树的直径】洛谷_3629 [APIO2010]巡逻

题意

有一颗树,要在其中加入 K ( k ≤ 2 ) K(k\leq 2) K(k2)条边,使得本来遍历这颗树要经过的边数最少,同时加入的边一定要正好走过 1 1 1次。

思路

K = 1 K=1 K=1时,显然是在树的直径的两个点之间连一条边,因为这样可以少走一次直径,而直径又是最长的。
K = 2 K=2 K=2时,新建的边如果与之前的边没有环重叠的话也是和 K = 1 K=1 K=1的方法一样。如果有重叠的话,那么要正好走过一次新建的边,我们重复的边就要都多走一次。
综上所述,可以这样做:
1 ) 1) 1)先求一次树的直径,记为 D 1 D_1 D1,然后把直径上的边取反。
2 ) 2) 2)再求一次直径,记为 D 2 D_2 D2
答案为 2 ( N − 1 ) − ( D 1 − 1 ) − ( D 2 − 1 ) 2(N-1)-(D_1-1)-(D_2-1) 2(N1)(D11)(D21),如果我们取到重复的边,因为它是负的,根据初中数学,可以知道它会加回来。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>

struct node{
    int to, next, v;
}e[200001];
int N, K, tot = 1, l, r, d;
int head[100001], pre[100001], v[100001], f[100001];

void add(int x, int y) {
    e[++tot].to = y;
    e[tot].next = head[x];
    e[tot].v = 1;
    head[x] = tot;
}

void dfs1(int p, int fa, int w) {
    if (w >= d) {
        d = w;
        l = p;
    }
    for (int i = head[p]; i; i = e[i].next) {
        if (e[i].to != fa)
            dfs1(e[i].to, p, w + e[i].v);
    }
}

void dfs2(int p, int fa, int w) {
    if (w >= d) {
        d = w;
        r = p;
    }
    for (int i = head[p]; i; i = e[i].next) {
        if (e[i].to != fa) {
            pre[e[i].to] = p;
            dfs2(e[i].to, p, w + e[i].v);
        }
    }
}

void dfsD() {//dfs求树的直径
    d = 0;
    dfs1(1, 0, 0);
    d = 0;
    dfs2(l, 0, 0);
}

void change(int p) {//暴力回溯改边
    if (p == l) return;
    for (int i = head[p]; i; i = e[i].next) {
        if (e[i].to == pre[p]) {
            e[i].v = -1;
            e[i ^ 1].v = -1;
            change(e[i].to);
        }
    }
}

void dp(int x) {
    v[x] = 1;
    for (int i = head[x]; i; i = e[i].next) {
        if (v[e[i].to]) continue;
        dp(e[i].to);
        d = std::max(d, f[x] + f[e[i].to] + e[i].v);
        f[x] = std::max(f[x], f[e[i].to] + e[i].v);
    }
}

int main() {
    scanf("%d %d", &N, &K);
    for (int i = 1, a, b; i < N; i++) {
        scanf("%d %d", &a, &b);
        add(a, b);
        add(b, a);
    }
    dfsD();
    if (K == 1) {
        printf("%d", 2 * N - 1 - d);
        return 0;
    } else {
        int s = 2 * N - d;
        change(r);
        d = 0;
        dp(1);//边权有负所以用树形dp
        printf("%d", s - d);
        return 0;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值