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

本文探讨了在树结构中通过添加最多两条边以最小化遍历边数的问题,详细阐述了当添加一条或两条边时的最优策略,并通过树的直径求解及边权重修改实现了算法。代码使用C++实现,涉及树形DP和边权重处理。

题意

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

思路

K=1K=1K=1时,显然是在树的直径的两个点之间连一条边,因为这样可以少走一次直径,而直径又是最长的。
K=2K=2K=2时,新建的边如果与之前的边没有环重叠的话也是和K=1K=1K=1的方法一样。如果有重叠的话,那么要正好走过一次新建的边,我们重复的边就要都多走一次。
综上所述,可以这样做:
1)1)1)先求一次树的直径,记为D1D_1D1,然后把直径上的边取反。
2)2)2)再求一次直径,记为D2D_2D2
答案为2(N−1)−(D1−1)−(D2−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、付费专栏及课程。

余额充值