Codeforces 1329C - Drazil Likes Heap(堆+贪心)

栏目: IT技术 · 发布时间: 4年前

内容简介:给出一个高度为 h 的大根堆, 要求弹出其中若干个数后高度变为 g, 并且前后大根堆都是满二叉树. 问新的大根堆所有数之和的最小值, 并要给出一种弹出数的操作序列(节点序号). h, g ≤ 20. 堆的弹出过程见如下代码:

题目链接

题意

给出一个高度为 h 的大根堆, 要求弹出其中若干个数后高度变为 g, 并且前后大根堆都是满二叉树. 问新的大根堆所有数之和的最小值, 并要给出一种弹出数的操作序列(节点序号). h, g ≤ 20. 堆的弹出过程见如下代码:

Codeforces 1329C - Drazil Likes Heap(堆+贪心)

题解

考虑新堆的每个节点的可能最小结果: 如果是叶子节点, 那么最小是取它的子树中最小的数; 否则, 取子树中比两个儿子大的数中的最小值.

那么, 能否能得到这个每个节点都取到最小值的堆呢, 回答是肯定的. 我们在得知哪些数字会留下后, 是要把其他点弹出的. 弹出这些数后, 我们还需要考虑新堆是不是满二叉树. 按序号从小到大考虑每个节点, 会发现(这个过程不太好描述, 主要还是堆的性质)每个节点最后的结果一定是以它为根的子树中剩余数字的最大值(被祖先节点使用的话是要移除的), 既然每个节点都有数字, 这个堆自然是满二叉堆.

要得到新堆的每个节点的数字, 可以从下到上维护每个节点的子树的序列. 每次合并都是 O(len) (len为序列长度) 的, 总的时间复杂度为 O(nlogn). 要输出操作序列时, 可以从下往上弹出不需要的数字, 这样不会影响到祖先的结构.

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inc(i, l, r) for (int i = l; i <= r; i++)
#define dec(i, l, r) for (int i = l; i >= r; i--)

const int maxn = (1 << 20) + 5;

int t, h, g;
int val[maxn], nxt[maxn];
vector<int> sub[maxn];

inline vector<int> Merge(vector<int> v1, vector<int> v2, int id) {
    int sz1 = v1.size(), sz2 = v2.size();
    vector<int> r(sz1 + sz2 + 1);
    for (int i = 0, j = 0; i + j < sz1 + sz2;) {
        if (j == sz2 || i < sz1 && v1[i] < v2[j]) {
            r[i + j] = v1[i];
            i++;
        } else {
            r[i + j] = v2[j];
            j++;
        }
    }
    r[sz1 + sz2] = val[id];
    return r;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> t;
    while (t--) {
        cin >> h >> g;
        inc(i, 1, (1 << h) - 1) cin >> val[i];
        inc(i, 1 << h - 1, (1 << h) - 1) sub[i] = vector<int>(1, val[i]);
        dec(i, (1 << h - 1) - 1, 1) {
            sub[i] = Merge(sub[i << 1], sub[i << 1 | 1], i);
        }
        set<int> s;
        ll sum = 0;
        inc(i, 1 << g - 1, (1 << g) - 1) {
            nxt[i] = sub[i][0];
            s.insert(nxt[i]);
            sum += nxt[i];
        }
        dec(i, (1 << g - 1) - 1, 1) {
            inc(j, 0, (int)sub[i].size() - 1) if (sub[i][j] > nxt[i << 1] &&
                                                  sub[i][j] > nxt[i << 1 | 1]) {
                nxt[i] = sub[i][j];
                s.insert(nxt[i]);
                sum += nxt[i];
                break;
            }
        }
        cout << sum << "\n";
        dec(i, (1 << h) - 1, 1) {
            if (s.find(val[i]) == s.end()) cout << i << " ";
        }
        cout << "\n";
    }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员修炼之道(影印版)

程序员修炼之道(影印版)

Andrew Hunt、David Thomas / 中国电力出版社 / 2003-8-1 / 39.00

本书直击编程陈地,穿过了软件开发中日益增长的规范和技术藩篱,对核心过程进行了审视——即根据需求,创建用户乐于接受的、可工作和易维护的代码。本书包含的内容从个人责任到职业发展,直至保持代码灵活和易于改编重用的架构技术。从本书中将学到防止软件变质、消除复制知识的陷阱、编写灵活、动态和易适应的代码、避免出现相同的设计、用契约、断言和异常对代码进行防护等内容。一起来看看 《程序员修炼之道(影印版)》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具