Codeforces Round #720 (Div. 2) D - Nastia Plays with a Tree

场上糊了一个小时都没糊出来(我甚至写了个假算法前半个多小时还没找到反例)

因为题目最后要求我们得到的是一条链,删除的边的数量和加边的数量一样多,所以我们要让删除的边最少。又因为我们要得到的一条链肯定是由多条组合起来的,而原树是由多条链构成的,我们最少要删除链数-1条边,那么问题转换成了如何把树划分成最少的链数。

考虑用贪心解决,在dfs到某个点u的时候,先处理u的子树,得到了一些仍然在和u连接的子节点,

1. 如果子节点的数量为1,那么就继续把这个链往上传。

2. 如果=2,就把这两个子节点合并成一条链,并把u和u的父亲断开

3. 如果>2,就把某两个节点合并成一条链,剩下的拆成一条一条的链

为什么这样是最优的呢,我们希望当前点u对u的父亲影响尽可能的小,所以在=2的时候,既然一定是把某两个点之间分离,变成两条不同的链,那把两个子节点合并成一条链,可以让u的父亲度数-1,让u的父亲有可能划分出更少的链,>2的时候也是同理。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector < int > edge[N];
vector < tuple <int, int, int, int> > ans;
int md;
int dfs(int x, int f) {
    int ch = 0, t = x;
    for (int u:edge[x]) {
        if (u == f)    continue;
        int v = dfs(u, x);
        if (!v)    continue; 
        ch++;
        if (ch == 1)    t = v;
        else if (ch == 2)    ans.emplace_back(x, f, v, md), md = t;
        else ans.emplace_back(u, x, u, md), md = v;    
    }    
    return (ch <= 1) ? t : 0; 
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        int n;
        scanf("%d", &n);
        for (int i = 1; i < n; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            edge[x].push_back(y);
            edge[y].push_back(x);
        }
        md = 1;
        while (edge[md].size() != 1)    md++;
        dfs(md, 0);
        printf("%d\n", ans.size());
        for (auto [a, b, c, d]:ans)
            printf("%d %d %d %d\n", a, b, c, d);
        ans.clear();
        for (int i = 1; i <= n; i++)    edge[i].clear();
    }
    return 0;
}

 

posted @ 2021-05-10 19:31  cminus  阅读(94)  评论(0编辑  收藏  举报