Problem 1894 志愿者选拔
Accept: 2275 Submit: 6920 Time Limit: 1500 mSec Memory Limit : 32768 KB
Problem Description
世博会马上就要开幕了,福州大学组织了一次志愿者选拔活动。
参加志愿者选拔的同学们排队接受面试官们的面试。参加面试的同学们按照先来先面试并且先结束的原则接受面试官们的考查。
面试中每个人的人品是主要考查对象之一。(提高人品的方法有扶老奶奶过街,不闯红灯等)
作为主面试官的John想知道当前正在接受面试的同学队伍中人品值最高的是多少。于是他请你帮忙编写一个程序来计算。
Input
输入数据第一行为一整数T,表示有T组输入数据。
每组数据第一行为”START”,表示面试开始
接下来的数据中有三种情况:
输入 | 含义 | |
---|---|---|
1 | C NAME RP_VALUE | 名字为NAME的人品值为RP_VALUE的同学加入面试队伍。(名字长度不大于5,0 <= RP_VALUE <= 1,000,000,000) |
2 | G | 排在面试队伍最前面的同学面试结束离开考场。 |
3 | Q | 主面试官John想知道当前正在接受面试的队伍中人品最高的值是多少。 |
最后一行为”END”,表示所有的面试结束,面试的同学们可以依次离开了。
所有参加面试的同学总人数不超过1,000,000
Output
对于每个询问Q,输出当前正在接受面试的队伍中人品最高的值,如果当前没有人正在接受面试则输出-1。
Sample Input
2
START
C
Tiny 1000000000
C
Lina 0
Q
G
Q
END
START
Q
C
ccQ 200
C
cxw 100
Q
G
Q
C
wzc 500
Q
END
Sample Output
1000000000
0
-1
200
100
500
Hint
数据较大建议使用scanf,printf 不推荐使用STL
Source
福州大学第七届程序设计竞赛
解决方案1
采用链表存储队列,查询最大RP的面试者则遍历链表输出。
#include<iostream>
typedef struct people {
char name[6];
int RP;
struct people *next;
}people;
people* init(char name[6],int RP) {
people* node;
node = (people *)malloc(sizeof(people));
strcpy(node->name, name);
node->RP = RP;
node->next = NULL;
return node;
}
void enqueue(people *&rear, char name[6], int RP) {
people* node;
node = init(name, RP);
rear->next = node;
rear = rear->next;
}
void dequeue(people *&head) {
head = head->next;
}
int findmaxRP(people *head) {
if (head == NULL) {
return -1;
}
int max = 0;
while (head != NULL) {
max = head->RP > max ? head->RP : max;
head = head->next;
}
return max;
}
void freequeue(people *&head) {
people *freenode;
while (head) {
freenode = head;
head = head->next;
free(freenode);
}
}
int main() {
int T;
char name[6];
int RP;
people *head = NULL, *rear;
scanf("%d", &T);
while (T) {
scanf("%s", name);
switch (name[0]) {
case 'C':
scanf("%s", name);
scanf("%d", &RP);
if (head == NULL) {
head = init(name, RP);
rear = head;
}
else {
enqueue(rear, name, RP);
}
break;
case 'Q':
printf("%d\n", findmaxRP(head));
break;
case 'G':
dequeue(head);
break;
case 'E':
memset(name, '\0', 6);
freequeue(head);
--T;
break;
}
}
}
当然,很自然的就Time Limit Exceed
解决方案2
在解决方案一中,找出队列中RP最大的面试者采用的是遍历队列,这样,每次遇到‘Q’时每次都要遍历一次队列,这导致时间复杂度大得有够呛。
事实上,每次改变队列时(出队或入队),只要RP最大者没有改变,也就是说最大者没有出队,或新入队者RP值没有大于原有的RP王者,那么遇到‘Q’时就可直接输出RP最大值。这样一来,时间复杂度比起每次都遍历队列来说,大大地优化了,可喜可贺,可喜可贺。
思路至此,就引出了新的知识点:
单调队列
定义:
- 1、维护区间最值
- 2、去除冗杂状态
- 3、保持队列单调,最大值是单调递减序列,最小值反之
- 4、最优选择在队首
具体步骤:
首先应当为每个面试者构造一个结构体,用于存储RP值和下标。多个结构体构成一个队列。
其次每次面试者入队,应当保持RP王者位于队首。保持的方法如下:
- 若队列为空,将A[i]从队尾入队
- 若队列不为空,将比A[i]小的元素都从队首弹出,然后把A[i]入队
- 若队列不为空且A[i]小于队尾,则直接从队尾把A[i]入队
举例:
RP数列为:6 4 10 10 8 6 7
首先插入第一个面试者,其RP值为6,位置(下标)为0,根据具体步骤1
即队列为:(6,0)
插入第二个面试者,其RP值为4,位置(下标)为1,根据具体步骤3
即队列为:(6,0)(4,1)
插入第三个面试者,其RP值为10,位置(下标)为2,根据具体步骤2
队列为:(10,2)
依此类推
队列状态分别为:
(10,3)
(10,3)(8,4)
(10,3)(8,4)(6,5)
(10,3)(8,4)(7,6)//此处应当注意,7<10但7>6,故(6,5)属于冗杂状态,应当去除
在本题中,只要入队的步骤运用了单调队列,就能使RP王者保持在队首,遇到‘Q’指令时只要输出队首的RP值即可,不必遍历整个队列。出队操作也可根据下标,在适当的时候将RP王者踢出队伍。例如在如上举例的队列状态(10,3)(8,4)(7,6)中,只要出现第4次‘G’时,队列状态即变为(8,4)(7,6)。
OK! Shut up and show me the code.
#include<iostream>
struct node {
int RP;//RP值
int num;//下标
}queue[1000010];
int main() {
int T;
char name[6];
int RP;
//head指向队首,clean表示已出队人数,rear指向队尾,num表示已入队人数
int head = 1, clean = 0, rear = 1, num = 0;
queue[1].RP = 0;
queue[1].num = 0;
scanf("%d", &T);
while (T) {
scanf("%s", name);
switch (name[0]) {
case 'C':
scanf("%s %d", name, &RP);
++num;
if (RP >= queue[head].RP) {
queue[rear].num = num;
queue[rear].RP = RP;
head = rear;
++rear;
}
//如果新入队的面试者RP值不大于RP王者,应当寻找到适当位置并插入
else {
//从rear位置开始往前寻找,直到RP<queue[rear-1].RP
while (RP >= queue[--rear].RP);
++rear;
queue[rear].num = num;
queue[rear].RP = RP;
++rear;
}
break;
case 'Q':
if (clean < num) {
printf("%d\n", queue[head].RP);
}
else
printf("-1\n");
break;
case 'G':
//如果RP王者出队,下一个RP王者即为++head
if (++clean == queue[head].num) {
++head;
}
break;
case 'E':
//应当对如下数据进行初始化
head = 1, clean = 0, rear = 1, num = 0;
queue[1].RP = 0;
queue[1].num = 0;
--T;
break;
}
}
}