PAT-BASIC1014——Waiting in Line
題目描述:
題目翻譯:
1014 排隊
假設一家一行共有N個視窗開放提供服務。每個視窗前面有一條黃線,將等待區域分成兩部分。客戶的等待規則如下:
每個視窗前面黃線以內的區域足夠容納M個客戶。因此,當N個視窗的該區域都滿時,所有編號在(NM + 1)以及(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)考慮一個事實,當一位客戶進入某一視窗的佇列時,他的服務結束時間就已經確定了,即當前在該視窗排隊的人的所有人的服務時間之和
(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將作為剛入隊的客戶的服務結束時間(即作為答案)儲存下來。
(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++解題報告: