【洛谷 P3871】[TJOI2010] 中位数 题解(优先队列+对顶堆)

170 阅读3分钟

[TJOI2010] 中位数

题目描述

给定一个由 NN 个元素组成的整数序列,现在有两种操作:

  • 1 add a\texttt{1 add }\textit{a}:在该序列的最后添加一个整数 aa,组成长度为 N+1N + 1 的整数序列。
  • 2 mid\texttt{2 mid}:输出当前序列的中位数。

中位数是指将一个序列按照从小到大排序后处在中间位置的数。(若序列长度为偶数,则指处在中间位置的两个数中较小的那个)

11[1,2,13,14,15,16][1, 2, 13, 14, 15, 16] 中位数为 1313
22[1,3,5,7,10,11,17][1, 3, 5, 7, 10, 11, 17] 中位数为 77
33[1,1,1,2,3][1, 1, 1, 2, 3] 中位数为 11

输入格式

第一行为初始序列长度 NN。第二行为 NN 个整数,表示整数序列,数字之间用空格分隔。第三行为操作数 MM,即要进行 MM 次操作。下面为 MM 行,每行输入格式如题意所述。

输出格式

对于每个 mid\verb!mid! 操作输出中位数的值。

样例 #1

样例输入 #1

6
1 2 13 14 15 16
5
add 5
add 3
mid
add 20
mid

样例输出 #1

5
13

提示

数据范围及约定

  • 对于 30%30\% 的数据,1N10,0001 ≤ N ≤ 10,0000M1,0000 ≤ M ≤ 1,000
  • 对于 100%100\% 的数据,1N100,0001 ≤ N ≤ 100,0000M10,0000 ≤ M ≤ 10,000

序列中整数的绝对值不超过 10910^9,序列中的数可能有重复。


思路

中位数是指在一个数列中,如果将这个数列按照从小到大排序,那么位于数列中间位置的数就是这个数列的中位数。对于有偶数个元素的数列,通常的定义是取中间两个数的平均值作为中位数,但在这段代码中,中位数被定义为中间两个数中的较小者。

这个数据结构使用了两个优先队列,一个是最大堆,一个是最小堆。最大堆存储的是数列中较小的一半的数,最小堆存储的是数列中较大的一半的数。这样,最大堆的堆顶就是数列中的中位数。

在插入一个新的数时,首先判断这个数是否小于最小堆的堆顶,如果是,那么就把这个数插入到最大堆中,否则就插入到最小堆中。然后,如果最小堆的大小超过了最大堆,就把最小堆的堆顶元素移动到最大堆中。如果最大堆的大小超过了最小堆的大小加一,就把最大堆的堆顶元素移动到最小堆中。这样可以保证最大堆和最小堆的大小差距不超过一,从而保证最大堆的堆顶是中位数。

在主函数中,首先读入一个整数n,然后读入n个整数并插入到数据结构中。然后读入一个整数m,接着进行m次操作。每次操作是一个字符串,如果字符串是"add",那么就读入一个整数并插入到数据结构中,否则就输出当前的中位数。


AC代码

#include <iostream>
#include <queue>
#define AUTHOR "HEX9CF"
using namespace std;

int n, m;
int cnt = 0;
priority_queue<int> hmax;
priority_queue<int, vector<int>, greater<int>> hmin;

void push(int x) {
	if (!hmin.empty() && x < hmin.top()) {
		hmax.push(x);
	} else {
		hmin.push(x);
	}
	cnt++;
	while (hmin.size() > hmax.size()) {
		hmax.push(hmin.top());
		hmin.pop();
	}
	while (hmax.size() > hmin.size() + 1) {
		hmin.push(hmax.top());
		hmax.pop();
	}
}

int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int t;
		cin >> t;
		push(t);
	}
	cin >> m;
	for (int i = 1; i <= m; i++) {
		string op;
		cin >> op;
		if (op == "add") {
			int t;
			cin >> t;
			push(t);
		} else {
			cout << hmax.top() << endl;
		}
	}
	return 0;
}