Codeforces Round 611 (Div. 3) F. DIY Garland(思维+构造)

原题链接:F. DIY Garland


题目大意:


给出一棵 n n n 个点的树,第 i i i 个点的权值为 2 i 2^{i} 2i ,定义第 j j j 条边的权值为其连接的两点 u , v u,v u,v 中深度较大的点的子树的权值和。

边按照边权由大到小的顺序给出每条边深度较小的点的编号,现在请构造一棵满足如上情况的原树,输出根以及连边情况。

解题思路:


注意到第 i i i 个点的点权是 2 i 2^{i} 2i,也就是说不会有点权重合的情况,且点权都互相严格大于或小于。

又因为边是由边权大到边权小的顺序给出的,那么我们第一条边深度较浅的点一定是根节点。

感性证明一下,由于每个点点权都是 2 i 2^{i} 2i 的形式,那么我们点权的加和也一定互不相同,即每条边边权都不同,假设边权为最大的边 ( v , y ) (v,y) (v,y) 所连深度较小的点( 我们设为 v v v )不是根节点,那么我们根节点( 我们设为 r o o t root root )由于是整棵树的根,一定有 d e p r o o t < d e p v dep_{root} < dep_{v} deproot<depv,设 v v v 的某个父亲 x x x 和根节点 r o o t root root 有直接连边 ( r o o t , x ) (root,x) (root,x) ,又边权互不同,那么我们一定会存在一条边 ( r o o t , x ) (root,x) (root,x) 的权值大于 ( v , y ) (v,y) (v,y) 的权值,和 ( v , y ) (v,y) (v,y) 的权值最大矛盾,故权值最大的边的最小点一定是根节点 r o o t root root,证毕。

考虑怎么构造:注意到一个问题,我们都只会获得深度较小的点,而那些边所连的点一定会有一些深度较大的点不会在输入中出现,而这些点一定是这棵树的叶子节点。

一个点在输入中的出现次数一定是它子树中儿子节点的个数,我们先将其较小的子树构造完后再构造它的子树,这样做是最方便的,而且也是有正确性的。

我们开一个按编号排序的小根堆,将所有叶子都保存进去,然后倒序枚举给出的边集,让权值最小的边深度最小的点和权值最小的叶子节点相连,当其子树内的点都连完时,它又成为了叶子节点,扔入小根堆,可知这样可以构造一组合法的解( 因为 2 i > 2 i − 1 + 2 i − 2 + . . . + 2 1 2^{i}>2^{i-1}+2^{i-2}+...+2^{1} 2i>2i1+2i2+...+21 ,所以这样贪心一定会使得按顺序构造的边 w 1 < w 2 < . . . < w n w_{1}<w_{2}<...<w_{n} w1<w2<...<wn)。

时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)

AC代码:

#include <bits/stdc++.h>
#define YES return void(cout << "Yes\n")
#define NO return void(cout << "No\n")
using namespace std;

using u64 = unsigned long long;
using PII = pair<int, int>;
using i64 = long long;

void solve() {
    int n;
    cin >> n;

    priority_queue<int, vector<int>, greater<int>> heap;

    vector<int> arr(n - 1), in(n + 1);
    for (auto& v : arr) {
        cin >> v;
        ++in[v];
    }

    for (int i = 1; i <= n; ++i) {
        if (!in[i]) {
            heap.push(i);
        }
    }

    reverse(arr.begin(), arr.end());

    vector<PII> edge;
    for (auto& u : arr) {
        edge.emplace_back(u, heap.top());
        heap.pop();
        if (--in[u] == 0) {
            heap.push(u);
        }
    }

    cout << arr.back() << '\n';
    while (edge.size()) {
        auto [u, v] = edge.back();
        cout << u << " " << v << '\n';
        edge.pop_back();
    }
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1; //cin >> t;
    while (t--) solve();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值