CF1041E 树的重建

1 CF1041E 树的重建

2 题目描述

\(Monocarp\) 画了一棵树(一个无向连接的无环图),然后给每个顶点都加了一个索引。每个索引都是从 \(1\)\(𝑛\) 的不同数字。对于这棵树的每一条边 \(𝑒\)\(Monocarp\) 都写了两个数字:如果边 \(𝑒\) 从树中删除后形成的两个子树顶点的最大索引。
\(Monocarp\) 给了你一个 \(𝑛−1\) 对数字的列表,如果存在这样的树则输出,否则输出 \(NO\)

3 题解

这是一道比较复杂的构造题。

我们容易发现,如果有任意一个 \(b_i\) 不是 \(n\),那么我们一定无法构造出来这个树。也就是说,这棵树长什么样跟 \(b_i\) 完全没关系。然后我们发现:这道题中貌似对于给出的树边的顺序也没有限制:我们可以以 \(a_i\) 为关键字进行从小到大的排序。容易看出:如果我们可以构造出这棵树,那么这棵树肯定是一个链表:\(n\) 为一端(根节点),\(1\) 为一端(叶子节点)。

构造时,我们可以考虑一个肯定可以构造出来的策略:如果 \(a_i > a_{i-1}\),那么直接把 \(a_{i-1}\) 变成 \(a_i\) 的子节点。这样我们在切断它们之间的边时,\(a_i\) 跑到 \(n\) 那一边,由于没有 \(n\) 大而无法展现出来。而只要保证 \(a_{i-1}\) 那一边 \(a_{i-1}\) 是最大的就可以了。如果 \(a_i = a_{i-1}\),那么再找一个比 \(a_i\) 小的值作为 \(a_i\) 的父亲节点即可:这样会留下两条可以供我们切的线段:一条是我们找来的数和根节点的,一条是我们找来的数和 \(a_i\) 的。随意切段一条所求出的较小值都为 \(a_i\)。如果在第二步没找到那个数,说明不能构成一棵树,输出不可行即可。

4 代码(空格警告):

#include <iostream>
using namespace std;
const int N = 1005;
int n, a, b, last, sum;
int cnt[N];
bool f[N];
int main()
{
    cin >> n;
    for (int i = 1; i < n; i++)
    {
        cin >> a >> b;
        if (b != n)
        {
            cout << "NO";
            return 0;
        }
        cnt[a]++;
    }
    for (int i = 1; i <= n; i++)
    {
        sum += cnt[i];
        if (sum > i)
        {
            cout << "NO";
            return 0;
        }
    }
    last = -1;
    cout << "YES" << endl;
    for (int i = 1; i <= n; i++)
    {
        if (cnt[i] >= 1)
        {
            f[i] = 1;
            if (last != -1) cout << last << " " << i << endl;
            last = i;
            cnt[i]--;
        }
        if (cnt[i] == 0) continue;
        for (int j = 1; j <= n; j++)
        {
            if (!f[j])
            {
                cout << j << " " << last << endl;
                cnt[i]--;
                last = j;
                f[j] = 1;
                if (!cnt[i]) break;
            }
        }
    }
    cout << last << " " << n;
    return 0;
}

欢迎关注我的公众号:智子笔记

posted @ 2021-02-05 16:17  David24  阅读(43)  评论(0编辑  收藏  举报