uoj280 【UTR #2】题目难度提升 堆维护中位数+set

题目传送门

http://uoj.ac/problem/280

题解

这道题很妙啊。

这种题目如果给予选手足够的时间,每一个选手应该都能做出来。

大概就是核心思路看上去很简单,但是想要推出来并不简单。


首先考虑如果没有重复的元素应该怎么做。

第一个数应该就是最小值。

在没有重复元素的时候,新加入一个数想要保证中位数不下降就必须要满足这个数大于等于前面的中位数。

所以选择加入某个数的时候的判断条件就是加入这个数以后,剩下的最小的数比中位数大。

具体实现的时候,可以先取出这个最小的数。如果前面的元素数量是奇数,那么加入它以后中位数是中间两个数的平均值,所以需要讨论这个最小的数和之前中位数的关系。如果大于之前的中位数,那么我们可以直接选择剩下的数里面最大的——因为不管加进去谁,以后的中位数都没有这个最小的数大,所以可以直接放心地加入最大的;否则,假设之前的中位数为 \(pp\),新加入的数为 \(x\),之前取出的最小数为 \(v\),那么之后的中位数 \(\frac{pp + x}2\)\(\geq v\)。所以 \(x\) 可以取 \(\leq 2v - pp\) 的最大的数。

如果之前元素数量是偶数,那么加入以后它的中位数就是一个固定的数,不随加入了哪个数改变。如果最小的数大于等于这个数,那么直接加入最大的都不会有影响;否则加入最小的数。

维护中位数可以使用堆。


考虑有了重复的元素的做法。

如果重复的元素在整个集合的 \(mid\) 的后面,那么不会影响。

如果经过 \(mid\) 那么可以直接以经过 \(mid\) 的这个重复元素的值的最后一位为开始,先输出这个数,然后输出小于等于这个数的最大数,然后大于这个数的最大的数,并删除;重复这个过程,知道后面被删光了。然后把剩下的从大到小输出就可以了。正确性很显然。

如果不经过呢,那么我们找到小于 \(mid\) 的最大的重复元素,先把小于这个重复元素的全部按照一个小于等于这个数的最大数,一个大于这个数的最大的数的方案输出完。下面应该只剩下大于这个重复元素的了,可以按照上面没有重复元素的做法。


时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back

template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}

typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;

template<typename I> inline void read(I &x) {
	int f = 0, c;
	while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
	x = c & 15;
	while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
	f ? x = -x : 0;
}

const int N = 1e5 + 7;

int n, pp, sz;
int a[N];
std::multiset<int> s;
std::priority_queue<int, std::vector<int>, std::greater<int> > q;

inline void qadd(int x) {
	++sz, q.push(x);
	while (q.size() * 2 > sz) pp = q.top(), q.pop();
}

inline void work() {
	std::sort(a + 1, a + n + 1);
	if (a[(1 + n) / 2] == a[(1 + n) / 2 + 1]) {
		int p = (1 + n) / 2, p1, p2 = n;
		while (p < n && a[p] == a[p + 1]) ++p;
		p1 = p;
		printf("%d ", a[p1--]);
//		dbg("******** %d %d\n", p1, p2);
		while (p1 && p2 > p) printf("%d %d ", a[p1--], a[p2--]);//, dbg("*** %d %d, -> %d\n", p1, p2, (1 + n) / 2);
		while (p1) printf("%d ", a[p1--]);
		return;
	}
//	dbg("****************\n");
	int p, p1, p2;
	for (p = (1 + n) / 2; p > 1 && a[p] != a[p - 1]; --p) ;
	if (p > 1 && a[p] == a[p - 1]) {
		p1 = p, p2 = n;
		printf("%d ", a[p1--]);
		while (p1 && p2 > p) printf("%d %d ", a[p1--], a[p2--]);
		for (int i = 1; i <= p1; ++i) s.insert(a[i]);
		for (int i = p1 + 1; i <= p; ++i) qadd(a[i]);
		for (int i = p + 1; i <= p2; ++i) s.insert(a[i]);
		for (int i = p2 + 1; i <= n; ++i) qadd(a[i]);
	} else {
		qadd(a[1]);
		printf("%d ", a[1]);
		for (int i = 2; i <= n; ++i) s.insert(a[i]);
	}
//	dbg("****************\n");
	while (!s.empty()) {
		int min = *s.begin();
		std::multiset<int>::iterator it;
		if (!(sz & 1)) {
			if (min < q.top()) it = s.begin();
			else it = s.end(), --it;
		} else {
			if (q.empty() || min * 2 < pp + q.top()) it = s.upper_bound(min * 2 - pp), --it;
			else it = s.end(), --it;
		}
		qadd(*it), printf("%d ", *it), s.erase(it);
	}
}

inline void init() {
	read(n);
	for (int i = 1; i <= n; ++i) read(a[i]);
}

int main() {
#ifdef hzhkk
	freopen("hkk.in", "r", stdin);
#endif
	init();
	work();
	fclose(stdin), fclose(stdout);
	return 0;
}
posted @ 2019-10-28 22:26  hankeke303  阅读(321)  评论(0编辑  收藏  举报