内容简介:给出一个高度为 h 的大根堆, 要求弹出其中若干个数后高度变为 g, 并且前后大根堆都是满二叉树. 问新的大根堆所有数之和的最小值, 并要给出一种弹出数的操作序列(节点序号). h, g ≤ 20. 堆的弹出过程见如下代码:
题意
给出一个高度为 h 的大根堆, 要求弹出其中若干个数后高度变为 g, 并且前后大根堆都是满二叉树. 问新的大根堆所有数之和的最小值, 并要给出一种弹出数的操作序列(节点序号). h, g ≤ 20. 堆的弹出过程见如下代码:
题解
考虑新堆的每个节点的可能最小结果: 如果是叶子节点, 那么最小是取它的子树中最小的数; 否则, 取子树中比两个儿子大的数中的最小值.
那么, 能否能得到这个每个节点都取到最小值的堆呢, 回答是肯定的. 我们在得知哪些数字会留下后, 是要把其他点弹出的. 弹出这些数后, 我们还需要考虑新堆是不是满二叉树. 按序号从小到大考虑每个节点, 会发现(这个过程不太好描述, 主要还是堆的性质)每个节点最后的结果一定是以它为根的子树中剩余数字的最大值(被祖先节点使用的话是要移除的), 既然每个节点都有数字, 这个堆自然是满二叉堆.
要得到新堆的每个节点的数字, 可以从下到上维护每个节点的子树的序列. 每次合并都是 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"; } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。