堆(优先队列)的灵活用法
我想大家都应该知道什么事堆了。所以,这里就不再介绍了。如果,不知道的话,可以自己去网上查一下资料。
堆在竞赛中运用非常的灵活,因此一般可以用堆的题目,都有另外的方法,只是一般都是堆实现起来比较容易写代码和容易理解罢了。说理论的知识我也不会,所以就直接给出两道题来说明一下堆是如何在问题中灵活运用的吧。
首先,先来看一道去年的腾讯马拉松的题目吧。不过,很多人都是直接用公式解的。我当时没想到,而想了挺久的然后想到了用堆模拟。
题目链接:Click Here~
题目分析:
给出N,K,M代表N个人,每个人要检查K次,而总共有M个医生。而其他细节自己看题目吧。
思路分析:
我当时想着题的时候是本着一个不浪费医生的原则来考虑的。如果,在人数够的情况下,一定不能让医生空闲。所以,我们可以想到只要每个人的次数,也就是剩下的项目越平均则是越好的。好好想想为什么?这个是解题的关键。所以,有了这个思路之后,就自然的会想到只要每次都把项目数最多的人先检查了,则剩下的人当中肯定会更平均的。这里就可以运用到了堆的优点,大的先出。而每个人最多进队列是N*K次,所以总时间是O(N*K*M)。所以,不会超时。
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 100 + 5;
struct People
{
int num;
bool operator < (const People &a)const{
return num < a.num;
}
};
int main()
{
int T,n,k,m;
scanf("%d",&T);
while(T--)
{
People tmp[N],people;
priority_queue<People> worker;
while(!worker.empty())worker.pop();
scanf("%d%d%d",&n,&k,&m);
people.num = k;
for(int i = 0;i < n;++i){
worker.push(people);
}
int ans = 0,cnt = 0;
while(!worker.empty())
{
ans++;
cnt= 0;
for(int i = 0;i < m;++i){ //m个医生挑出m个人(队列中人数大于m)
if(!worker.empty()){
tmp[cnt] = worker.top(); //printf("%d\n",tmp[cnt].num);
tmp[cnt].num--;
cnt++;
worker.pop();
}
else
break;
}
for(int i = 0;i < cnt;++i){
if(tmp[i].num > 0) worker.push(tmp[i]);
}
}
printf("%d\n",ans);
}
return 0;
}
还有一题是北师大的热身赛。
题目链接:Click Here~
A国军队数目众多,每一支军队的编制情况是相同的,最小的单位都是“排”,各个排的人数都是不确定的。
某日,Oo接到上级通知,要他去视察一下军队的情况。(诶?他传说中不是搞安全的么?怎么视察去了……)Oo去到军队一看,被他视察过的军队有一半以上因为各种传染病的缘故战斗力被大幅度的削弱了……上级了解到这个情况之后,立马作出反应,决定派遣军医对所有的军队以排为单位进行一次全面的体检。
由于各种原因,体检行动遇到了一些瓶颈。
首先,由于军医人手缺乏,一个排最多只能有一个军医为他们进行体检;
第二,每一个军人都会且仅会被检查一次;
第三,每一个军医每天检查的人数是一定的,当他们检查完一个排的军人之后会立刻转到另外一个排进行体检,转军队的时间可以认为是瞬时完成的(神一般的军医……);
第四,因为人数多的排往往战斗力高,所以要最先去检查当前还未被检查的排当中人数最多的排。
现在你的任务是帮助Oo计算出总体需要的体检时间。
Input
对于每一组数据:
第一行:三个整数N(0<N<=1000000)、M(0<M<=N)、P(0<P<=1000),N表示排的个数,M表示军医的数量,P表示一个军医一天可以体检的人数。
第二行:N个整数,表示每个排的人数(每排人数小于2000)。
Output
Sample Input
1
5 2 100
100 200 300 400 500
Sample Output
8.000
Source
改题跟上题很像,但是这题的每个人只能进入队列一次。但是,这道题跟上道题处理的时候还是有挺大的区别的。这道题要求是把选中了某一个排后就要把这个排的人全看完。则我们知道,最后的总时间就是某个医生用的最多的时间是多少答案就是多少。好好想想为什么?因为,最后一个医生完成任务时候是最晚的,而其他医生此时是闲这的。所以,用时最多的一定是这个最后完成任务的医生。所以,我们只要求出最后完成任务的医生的用时是多少就好了。而我们知道每个医生看完每排的人后,是要转到下一个排的。所以,此时我们的堆维护的是最少的人的排的,因为,根据模拟这个医生看完这个排的病人后是要转到下一个排。所以,此时我们的队列维护的就是最少人数的排。
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;
vector<int> iv;
priority_queue<int,vector<int>,greater<int> > Q;
void Init()
{
iv.clear();
while(!Q.empty())Q.pop();
}
bool cmp(const int a,const int b)
{
return a > b;
}
int main()
{
int T,n,m,p;
scanf("%d",&T);
while(T--)
{
Init();
int x;
scanf("%d%d%d",&n,&m,&p);
for(int i = 0;i < n;++i){
scanf("%d",&x);
iv.push_back(x);
}
sort(iv.begin(),iv.end(),cmp);
for(int i = 0;i < m;++i)
Q.push(iv[i]);
for(int i = m;i < n;++i){
int v = Q.top();
Q.pop();
v += iv[i];
Q.push(v);
}
while(Q.size()>1)Q.pop();
printf("%.3lf\n",Q.top()*1.0/p);
}
return 0;
}