两个堆维护已输出排名左侧与右侧元素,使每次 GET 的目标值位于右堆顶。
OJ: luogu
题目 ID: P1801
难度:普及+/提高
标签:双堆第k小heapqpython
日期: 2026-07-16 21:00
题意
元素按顺序加入;第 i 次 GET 输出当前集合第 i 小值。
思路
每次 GET 都要当前第
集合最多
目标排名只挪一位,需要保存的信息能否缩小到"分界线"附近?
第
新元素进来,怎么决定它属于哪一半?
把新数和左半的最大值比较:若比它还小,说明新数应该算进"已输出的前
GET 之后
这次输出的右堆顶就是第
lower 用负数最大堆保存已经越过的 upper 保存其余元素,小根堆顶就是下一答案。新值按分界线进入对应堆;每次输出 upper[0] 后,把它移入 lower,为下一排名作准备。
也可以换一个更直接的模型:用一个维护有序集合的平衡树(Treap),插入后直接用 kth 查询第
Python 知识
- 负数把
heapq变成最大堆。 heappushpop一次完成插入和弹出,比两个独立操作更直接。values[inserted:amount]对应两次 GET 间新增的连续输入段。
代码
双堆解法(C++)
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m, n;
cin >> m >> n;
vector<int> a(m);
for (int i = 0; i < m; i++) {
cin >> a[i];
}
vector<int> u(n);
for (int i = 0; i < n; i++) {
cin >> u[i];
}
priority_queue<int> L; // 最大堆,存储前 i-1 个最小元素
priority_queue<int, vector<int>, greater<int>> R; // 最小堆,存储剩余元素,堆顶为第 i 小
int inserted = 0;
for (int q : u) {
// 插入 a[inserted .. q-1]
for (int i = inserted; i < q; i++) {
int x = a[i];
if (!L.empty() && x < L.top()) {
L.push(x);
R.push(L.top());
L.pop();
} else {
R.push(x);
}
}
inserted = q;
// GET 操作
cout << R.top() << "\n";
L.push(R.top());
R.pop();
}
return 0;
}双堆解法(Python)
import heapq
import sys
data = iter(map(int, sys.stdin.buffer.read().split()))
value_count, query_count = next(data), next(data)
values = [next(data) for _ in range(value_count)]
queries = [next(data) for _ in range(query_count)]
lower = []
upper = []
inserted = 0
answers = []
for amount in queries:
for value in values[inserted:amount]:
if lower and value < -lower[0]:
heapq.heappush(upper, -heapq.heappushpop(lower, -value))
else:
heapq.heappush(upper, value)
inserted = amount
answers.append(str(upper[0]))
heapq.heappush(lower, -heapq.heappop(upper))
print("\n".join(answers))平衡树解法(C++)
#include <bits/stdc++.h>
using namespace std;
struct Treap {
struct Node {
int child[2] = {0, 0};
int value = 0;
int priority = 0;
int count = 0;
int size = 0;
};
vector<Node> tree;
int root = 0;
mt19937 rng;
Treap(int max_nodes = 0) : rng(712367821) {
tree.reserve(max_nodes + 1);
tree.push_back(Node()); // node 0 is the null sentinel.
}
int new_node(int value) {
tree.push_back(Node());
int id = (int)tree.size() - 1;
tree[id].value = value;
tree[id].priority = (int)rng();
tree[id].count = 1;
tree[id].size = 1;
return id;
}
int node_size(int u) const {
return u == 0 ? 0 : tree[u].size;
}
void pushup(int u) {
tree[u].size = node_size(tree[u].child[0]) +
node_size(tree[u].child[1]) +
tree[u].count;
}
// direction=0: lift left child by right rotation.
// direction=1: lift right child by left rotation.
void rotate(int &u, int direction) {
int v = tree[u].child[direction];
tree[u].child[direction] = tree[v].child[direction ^ 1];
tree[v].child[direction ^ 1] = u;
pushup(u);
pushup(v);
u = v;
}
void insert(int &u, int value) {
if (u == 0) {
u = new_node(value);
return;
}
if (tree[u].value == value) {
tree[u].count++;
pushup(u);
return;
}
int direction = value > tree[u].value;
insert(tree[u].child[direction], value);
if (tree[tree[u].child[direction]].priority < tree[u].priority) {
rotate(u, direction);
}
pushup(u);
}
void insert(int value) {
insert(root, value);
}
// Rank is 1-based: the smallest value has rank 1.
int rank_of(int value) const {
int u = root;
int rank = 1;
while (u != 0) {
if (value <= tree[u].value) {
u = tree[u].child[0];
} else {
rank += node_size(tree[u].child[0]) + tree[u].count;
u = tree[u].child[1];
}
}
return rank;
}
int kth(int k) const {
int u = root;
while (u != 0) {
int left_size = node_size(tree[u].child[0]);
if (k <= left_size) {
u = tree[u].child[0];
} else if (k <= left_size + tree[u].count) {
return tree[u].value;
} else {
k -= left_size + tree[u].count;
u = tree[u].child[1];
}
}
return -1;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m, n;
cin >> m >> n;
vector<int> a(m);
for (int i = 0; i < m; i++) {
cin >> a[i];
}
vector<int> u(n);
for (int i = 0; i < n; i++) {
cin >> u[i];
}
Treap treap(m + 5);
int inserted = 0;
int rank = 1; // 第 i 次 GET 输出第 i 小
for (int q : u) {
for (int i = inserted; i < q; i++) {
treap.insert(a[i]);
}
inserted = q;
cout << treap.kth(rank) << '\n';
rank++;
}
return 0;
}复杂度
每个元素移动常数次,总时间
平衡树解法插入和查询各
总结
目标排名每次只增加 1,双堆分界也只需向右移动一个元素。