P3629 [APIO2010] 巡逻(树的直径,树形DP)

传送门


题目大意

给出一棵树,现在根节点为 1 1 1号节点,每天都有警察从一号节点出发,到每条路上巡逻然后回到根节点。现在可以新建 k ( 1 ≤ k ≤ 2 ) k(1 \leq k \leq 2) k(1k2)条道路,新建的道路只能经过恰好一次,其余路经过至少一次,求新建道路后巡逻的最短距离。

解题思路

假设不连接道路,从 1 1 1号节点走到所有的节点再回去恰好走了每条边两次。可以手玩一下样例,然后就可以发现这个规律

  • k = 1 k=1 k=1时,不难发现,需要形成一个最大的基环树,联系到树的直径,其实就是求出树的直径 l e n len len然后连接起点终点,这样环上的边只走一次,总的次数为 2 ∗ ( n − 1 ) − l e n + 1 2*(n-1)-len+1 2(n1)len+1,样例同样给出了图示
  • k = 2 k=2 k=2时,可以猜想到是在 1 1 1的基础上再连接一个环形成两个环(这个可以证明,但是由于赶时间就不说了)。那么在第二个环和第一个环显然有以下几种关系
    – 第二个环和第一个环有若干个公共边
    – 第二个环和第一个环无公共边
    – 第二个环是第一个环的一部分
    如下图所示,连接 4 , 6 4,6 4,6即为第一种情况;连接 6 , 7 6,7 6,7即为第第二种情况;连接 1 , 8 1,8 1,8为第三种情况
    在这里插入图片描述
    显然在这个样例中第三种情况是一定被舍弃的。考虑第一种情况,即如果和第一个环重复的边有 x x x条,那么这 x x x条是需要走两次的,即答案为 2 ∗ ( n − 1 ) − l e n 1 + 1 − l e n 2 + 1 − x 2*(n-1)-len1+1-len2+1-x 2(n1)len1+1len2+1x,相当于这几条边不在所选择的路径上。这里用到一个非常奇妙的思想,我们设置重复的边的权值为-1,那么计算权值和(路径长度)时相当于直接减去了重复的这部分。因此只需要使用树形DP计算第二条路径,实际上就是新图的直径

注意点:

  • 在更新第一个直径的权值显然需要得到路径,那么第一个直径建议两次 d f s dfs dfs求,这样可以在记录下起点和终点然后递归求路径。因为我们存的是边,那么先记录下环上的点,然后对每个点的边集遍历,只有边的起点终点均出现在map中才更新这条边的权值(第一次我写的是记录pair,然后在dp时判断,但是这样TLE了一个点,我想可能是哈希冲突了)
  • 含负权的树求直径不能使用两次 d f s dfs dfs,需要使用树形DP
  • 因为可以自环,那么第二次求出的答案如果小于0就直接更新为0

//
// Created by Happig on 2020/8/19
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>

using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 4e5 + 10;

struct node {
    int next, to, w;
} edge[maxn];

int f[maxn], head[maxn], tot, maxd, s, t;
ll d[maxn], ans;
unordered_map<int, int> mp;

void add_edge(int u, int v, int w) {
    tot++;
    edge[tot] = {head[u], v, w};
    head[u] = tot;

    tot++;
    edge[tot] = {head[v], u, w};
    head[v] = tot;
}

void dfs(int u, int fa, int cur) {
    f[u] = fa;
    if (maxd < cur) {
        maxd = cur;
        t = u;
    }
    for (int i = head[u]; i; i = edge[i].next) {
        int v = edge[i].to;
        if (v != fa) dfs(v, u, cur + 1);
    }
}

void dp(int u, int fa) {
    // d[u] = (mp.count({u, fa}) || mp.count({fa, u}) ? -1 : 0);
    for (int i = head[u]; i; i = edge[i].next) {
        int v = edge[i].to, w = edge[i].w;
        if (v == fa) continue;
        dp(v, u);
        ans = max(ans, d[u] + d[v] + w);
        d[u] = max(d[u], d[v] + w);
    }
}

int main() {
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, k;
    cin >> n >> k;
    for (int i = 1, u, v; i < n; i++) {
        cin >> u >> v;
        add_edge(u, v, 1);
    }
    dfs(1, -1, 0);
    maxd = 0, s = t;
    dfs(t, -1, 0);
    //cout<<s<<" "<<t<<endl;
    if (k == 1) {
        cout << 2 * (n - 1) - maxd + 1 << "\n";
    } else {
        while (f[t] != -1) {
            mp[t] = 1;
            //cout<<t<<endl;
            t = f[t];
        }
        mp[t] = 1;
        for (int i = 1; i <= n; i++)
            if (mp.count(i)) {
                for (int j = head[i]; j; j = edge[j].next)
                    if (mp.count(edge[j].to)) edge[j].w = -1;
            }
        ans = -inf;
        dp(1, -1);
        //cout << maxd << " " << ans << endl;
        if (ans < 0) ans = 0;
        ans += maxd;
        cout << 2 * n - ans << "\n";
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值