洛谷 P1168 中位数

题目链接:点击这里



分析题意

题目可以理解为,先给你第一个数,输出他(第一次的中位数),在之后每泵入两个数,对已泵入的所有数取中位数输出。


时间复杂度分析

N N N 最大为 1 0 5 10^5 105,所以复杂度不可能超过 o ( N 2 ) o(N^2) o(N2),大概率是 o ( N × l o g N ) o(N \times logN) o(N×logN)


思路分析

拿到本题一开始手足无措,不知所云。以为是把输入数组排个序然后输出前 ( N + 1 ) / 2 (N + 1) / 2 (N+1)/2 个数字,于是写了个小顶堆交上去了,接着就 WA 了。其实这是一道 对顶堆 题。

对顶堆

解释

正如他的名字一样,它是由两个堆的堆顶相接拼起来的。如图,保证数据 a[1] < mid < b[1。虽然两个堆内部可能并非有序,但是我们利用堆顶的性质,a[1]一定是大顶堆最大的,b[1]一定是小顶堆最小的,那么我们要找的数让它夹在这两个之间即可。

对顶堆

作用

对顶堆用作于找序列中第k个大(小)的数,简单来说就是找一个有序序列中的第 k k k 个数。

什么时候选择对顶堆

本题为何不用二分呢,二分也是从有序序列中找数啊?
本题多次询问且每次询问的区间不同,所以每次用二分之前需要对数组进行排序,一旦数据量大起来,我们负担不起这个时间消耗,所以我们用对顶堆。
简单来说,对顶堆用于在动态数组中选取第k个数


AC CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <vector>
#include <algorithm>
#define inf 0x3f3f3f3f
#define ll long long

using namespace std;

const int N = 1e5 + 10;
int n;
int h[N], a[N], b[N], cnt1, cnt2;


void up1(int u, int h[]){
	while(u / 2 && h[u / 2] < h[u]){
		swap(h[u / 2], h[u]);
		u >>= 1;
	}
}

void up2(int u, int h[]){
	while(u / 2 && h[u / 2] > h[u]){
		swap(h[u / 2], h[u]);
		u >>= 1;
	}
}

void down1(int u, int h[], int cnt){
	int t = u;
	if(u * 2 <= cnt && h[u * 2] > h[t]) t = u * 2;
	if(u * 2 + 1 <= cnt && h[u * 2 + 1] > h[t]) t = u * 2 + 1;
	if(t != u){
		swap(h[u], h[t]);
		down1(t, h, cnt);
	}
}

void down2(int u, int h[], int cnt){
	int t = u;
	if(u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
	if(u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
	if(t != u){
		swap(h[u], h[t]);
		down2(t, h, cnt);
	}
}

int main(){
	cin >> n;
	for(int i = 1; i <= n; ++i) scanf("%d", &h[i]);
	
	int mid = h[1];
	cout << h[1];
	
	for(int i = 2; i <= n; ++i){
		if(h[i] > mid){
			b[++cnt2] = h[i];
			up2(cnt2, b);
		}else{
			a[++cnt1] = h[i];
			up1(cnt1, a);
		}
		
		if(i % 2 == 1){
			while(cnt1 != cnt2){
				if(cnt1 < cnt2){
					a[++cnt1] = mid;
					up1(cnt1, a);
					mid = b[1];
					b[1] = b[cnt2--];
					down2(1, b, cnt2);
				}else{
					b[++cnt2] = mid;
					up2(cnt2, b);
					mid = a[1];
					a[1] = a[cnt1--];
					down1(1, a, cnt1);
				}
			}
			cout << endl << mid;
		}
	}
	
}

注意事项

  1. 本题中有两个不同的堆,所以对不同堆的操作也不同,意思就是说up, down要各写两个,不然肯定 WA笑死
  2. 其实不用在几个up, down函数中传入堆数组和计数器的,直接各自用各自的就行,只要不用错了。我这么写纯纯因为我是傻逼
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值