2020 CCF CSP-J2 第2题:直播获奖 <- 对顶堆

【问题描述】
NOI2130 即将举行。为了增加观赏性,CCF 决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为 w%,即当前排名前 w% 的选手的最低成绩就是即时的分数线。
更具体地,若当前已评出了 p 个选手的成绩,则当前计划获奖人数为 max(1,⌊p∗w%⌋),其中 w 是获奖百分比,⌊x⌋ 表示对 x 向下取整,max(x,y) 表示 x 和 y 中较大的数。如有选手成绩相同,则所有成绩并列的选手都能获奖,因此实际获奖人数可能比计划中多。
作为评测组的技术人员,请你帮 CCF 写一个直播程序。
题目内容详见洛谷P7072:https://www.luogu.com.cn/problem/P7072

【输入格式】
第一行有两个整数 n,w。分别代表选手总数与获奖率。
第二行有 n 个整数,依次代表逐一评出的选手成绩。

【输出格式】
只有一行,包含 n 个非负整数,依次代表选手成绩逐一评出后,即时的获奖分数线。相邻两个整数间用一个空格分隔。

【算法分析】
本题可以利用
对顶堆来求解。对顶堆由一个大根堆G和一个小根堆L构成,且常以小根堆L的堆顶元素L.top()作为对顶堆的分界点,并满足G.top()<L.top()。换句话表述,就是“在对顶堆中,大根堆的元素都小于L.top(),小根堆的元素都大于L.top()”。显然,若输入的元素大于L.top(),就加入小根堆。反之,就加入大根堆。
对顶堆常见的示意图如下所示:


 

由于堆常借助于STL中的 priority_queue<int> 来实现,那么对顶堆便可利用 priority_queue<int> 同时声明一个大根堆G及一个小根堆L来实现。对顶堆代码如下所示:

priority_queue<int> G;    //大根堆 

priority_queue<int,vector<int>,greater<int> > L;    //小根堆 

其中的 priority_queue 中文意思为“优先队列”。priority_queue的基本操作与队列相同。不过需要特别提醒的是, priority_queue 的 push() 函数的作用是将元素插到队尾,并排序。若是大根堆,在队尾插入某个元素后,要与大根堆中已有元素按从大到小排序;若是小根堆,在队尾插入某个元素后,要与小根堆中已有元素按从小到大排序。
对顶堆常用于求解“
第K小的元素”及“第K大的元素”问题。因为优先队列priority_queue虽然不像数组那样支持随机访问,但它可以用O(1)的时间查询出堆顶元素。显然,利用对顶堆查询堆顶元素的时间复杂度与数组的随机访问的时间复杂度O(1)持平。特别是当遇到多次动态查询问题时,对顶堆就更高效。此外,对顶堆还可用于解决其他“第K小的元素”及“第K大的元素”问题的变形问题,如求前n个元素的中位数等问题。

求解“第K小的元素”问题的解题思路为:若求第K小的元素,先利用输入序列提供的前K个元素构建一个包括K个元素的大根堆G,后来的元素x先与大根堆G的堆顶元素G.top()比较,如果x>G.top(),则将x加入小根堆L;否则,将大根堆G的堆顶元素G.top()弹出后,将x加入大根堆G。当然,简洁设计方案是先利用输入序列提供的前K个元素构建一个包括K个元素的大根堆G,便可求解“第K小的元素”问题。

求解“
第K大的元素”问题的解题思路为:若求第K大的元素,先利用输入序列提供的前K个元素构建一个包括K个元素的小根堆L,后来的元素x先与小根堆L的堆顶元素L.top()比较,如果x<L.top(),则将x加入大根堆G;否则,将小根堆L的堆顶元素L.top()弹出后,将x加入小根堆L。当然,简洁设计方案是先利用输入序列提供的前K个元素构建一个包括K个元素的小根堆L,便可求解“第K大的元素”问题。

例如,下面代码是利用小根堆求“第K大的元素”问题的简洁设计方案的源代码。

#include<bits/stdc++.h>
using namespace std;

priority_queue<int,vector<int>,greater<int> > L; //Small Root Heap

int main() {
	int k;
	cin>>k;

	vector<int> v;
	int x;
	while(cin>>x) {
		v.push_back(x);
	}

	for(int i=0; i<v.size(); i++) {
		if(L.size()<k) {
			L.push(v[i]);
		} else if (v[i]>L.top()) {
			L.pop();
			L.push(v[i]);
		}
	}

	cout<<L.top()<<" ";

	return 0;
}

/*
in:
3
3 2 7 5 8 1

out:
5
*/


【算法代码】

#include<bits/stdc++.h>
using namespace std;

priority_queue<int> G; //Big Root Heap
priority_queue<int,vector<int>,greater<int> > L; //Small Root Heap

int main() {
	int n,w;
	cin>>n>>w;

	for(int i=1; i<=n; i++) {
		int x;
		cin>>x;
		if(L.empty()||x>L.top())
			L.push(x);
		else G.push(x);
		
		int p=max(1,(int)(i*w/100));
		
		while(L.size()<p) {
			L.push(G.top());
			G.pop();
		}
		
		while(L.size()>p) {
			G.push(L.top());
			L.pop();
		}
		
		cout<<L.top()<<" ";
	}

	cout<<endl;

	return 0;
}

/*
in1:
10 60
200 300 400 500 600 600 0 300 200 100
out1:
200 300 400 400 400 500 400 400 300 300

in2:
10 30
100 100 600 100 100 100 100 100 100 100
out2:
100 100 600 600 600 600 100 100 100 100
*/


【参考文献】
https://blog.csdn.net/Little_Fire/article/details/81096272

https://blog.csdn.net/qq_44691917/article/details/105217448
https://blog.csdn.net/qq_53904604/article/details/118077248
https://www.cnblogs.com/fusiwei/p/11432323.html
https://blog.csdn.net/m0_50330125/article/details/109606140

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值