我的PAT-ADVANCED代码仓:https://github.com/617076674/PAT-ADVANCED
原题链接:https://pintia.cn/problem-sets/994805342720868352/problems/994805498207911936
题目描述:
题目翻译:
1014 排队
假设一家银行共有N个窗口开放提供服务。每个窗口前面有一条黄线,将等待区域分成两部分。客户的等待规则如下:
每个窗口前面黄线以内的区域足够容纳M个客户。因此,当N个窗口的该区域都满时,所有编号在(NM + 1)以及其之后的客户都必须等待在黄线后面。
当进入黄线时,每个客户都会选择人数最少的队伍。如果有2条或2条以上人数一样的队伍,客户会选择窗口编号最小的窗口。
客户i需要Ti时间处理他/她的业务。
前N个客户将在08:00被服务。
现在给定每个客户的处理时间,你需要准确算出何时该客户的业务处理完毕。
举个例子,假设该银行有2个窗口且每个窗口黄线内可以容纳2人。有5个客户排队,其处理时间依次是1 2 6 4 3。在早上08:00,客户1在窗口1被服务,客户2在窗口2被服务。客户3会选择在窗口1等待,客户4会选择在窗口2等待。客户5会等待在黄线外。
在08:00,客户1的业务处理完毕,客户5进入窗口1的队伍,因为此时窗口1的队伍更短。客户2会在08:02离开,客户4会在08:06离开,客户3会在08:07离开,客户5会在08:10离开。
输入格式:
每个输入文件包含一个测试用例。每个测试用例第一行包含4个正整数:N(<= 20,代表窗口数),M(<= 10,代表每个窗口黄线内的最大容量),K(<= 1000,代表客户数量),以及Q(<=1000,代表查询数量)。
下一行给出K个正整数,代表K个客户的处理时间。
最后一行给出Q个正整数,代表来查询其业务处理完毕时间的客户。客户编号从1到K。
输出格式:
对来查询的Q的客户,每个用户以形式HH:MM,其中HH在[08, 17]范围内,MM在[00, 59]范围内,输出他/她的业务处理完毕时间。注意,银行每天17:00关门,对于那些17:00之前还得不到服务的客户,你需要输出“Sorry”。
输入样例:
2 2 7 5
1 2 6 4 3 534 2
3 4 5 6 7
输出样例:
08:07
08:06
08:10
17:00
Sorry
知识点:队列
思路:用队列模拟每个窗口的排队过程
(1)考虑一个事实,当一位客户进入某一窗口的队列时,他的服务结束时间就已经确定了,即当前在该窗口排队的人的所有人的服务时间之和。而在所有窗口排满后,剩余客户能够去排队的时间点是所有窗口最早结束的队首客户,也就是说,在所有窗口排满的情况下,每当有一个窗口的队首客户服务结束(结束时间相同的,窗口ID小的视为先结束),剩余客户的第一个就会排到那个窗口最后面去。于是可以为窗口建立一个结构体window,存放该窗口当前队伍的最后服务时间endTime和队首客户的服务结束时间popTime,并维护一个该窗口的排队队列q。
(2)在8:00,只要窗口的队列没满,就把客户按照窗口编号为0 1 2 ... (N - 1) 0 1 2 ... (N - 1) 0 1 2 ...的循环顺序进行入队,且在安排的过程中不断更新窗口的endTime和popTime,其中endTime将直接作为刚入队客户的服务结束时间(即作为答案)保存下来,而popTime仅在安排每个窗口的第一个客户时更新。
(3)如果(2)中已经把所有窗口排满(显然如果没有排满,就不存在剩余在黄线外的客户),那么在该步中将剩下的客户想办法入队。由(1)可知,在所有窗口排满的情况下,每当有一个窗口的队首客户服务结束(结束时间相同的,窗口ID小的视为先结束),剩余客户的第一个就会排到那个窗口最后面去。这样对每一个剩余的客户,可以选出当前所有窗口中popTime最小的窗口(popTime相同的选择窗口ID较小的),该客户将排到该窗口的队列后面,并更新该窗口的endTime和popTime,其中endTime将作为刚入队的客户的服务结束时间(即作为答案)保存下来,popTime需加上队列中第一个客户出队后的新的队首客户的处理时间。
(4)对每一个输入的查询客户编号,如果他的服务开始时间在17:00之后(含17:00),则输出“Sorry”;否则,输出他的服务结束时间。
时间复杂度和空间复杂度均是O(K)。
C++代码:
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
struct window{
int endTime, popTime; //窗口当前队伍的最后服务时间、队首客户的服务结束时间
queue<int> q; //队列
};
int changeToMinute(int hour, int minute);
int main(){
int N, M, K, Q;
scanf("%d %d %d %d", &N, &M, &K, &Q);
int needTime[K];
window windows[N];
for(int i = 0; i < K; i++){
scanf("%d", &needTime[i]); //读入服务需要时间
}
for(int i = 0; i < N; i++){
windows[i].popTime = windows[i].endTime = changeToMinute(8, 0); //初始化每个窗口的popTime和endTime为08:00
}
int inIndex = 0; //当前第一个未入队的客户编号
int result[K];
for(int i = 0; i < min(N * M, K); i++){
windows[inIndex % N].q.push(inIndex); //循环入队
windows[inIndex % N].endTime += needTime[inIndex]; //更新窗口的服务结束时间endTime
if(inIndex < N){
windows[inIndex].popTime = needTime[inIndex]; //对窗口的第一个客户,更新popTime
}
result[inIndex] = windows[inIndex % N].endTime; //当前入队的客户的服务结束时间直接保存作为答案
inIndex++;
}
for(; inIndex < K; inIndex++){ //处理剩余客户的入队
int idx = -1, minPopTime = 1000000000; //寻找所有窗口的最小popTime
for(int i = 0; i < N; i++){
if(windows[i].popTime < minPopTime){
idx = i;
minPopTime = windows[i].popTime;
}
}
//找到最小popTime的窗口编号为idex,下面更新该窗口的队列情况
windows[idx].q.pop(); //队首客户离开
windows[idx].q.push(inIndex); //客户inIndex入队
windows[idx].endTime += needTime[inIndex]; //更新该窗口队列的endTime
windows[idx].popTime += needTime[windows[idx].q.front()]; //更新该窗口的popTime
result[inIndex] = windows[idx].endTime; //客户inIndex的服务结束时间为该窗口的endTime
}
int num;
for(int i = 0; i < Q; i++){
scanf("%d", &num); //查询客户编号
if(result[num - 1] - needTime[num - 1] >= changeToMinute(17, 0)){
printf("Sorry\n");
}else{
printf("%02d:%02d\n", result[num - 1] / 60, result[num - 1] % 60);
}
}
return 0;
}
int changeToMinute(int hour, int minute){
return hour * 60 + minute;
}
C++解题报告: