1. 程式人生 > >PAT-BASIC1014——Waiting in Line

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)考慮一個事實,當一位客戶進入某一視窗的佇列時,他的服務結束時間就已經確定了,即當前在該視窗排隊的人的所有人的服務時間之和

。而在所有視窗排滿後,剩餘客戶能夠去排隊的時間點是所有視窗最早結束的隊首客戶,也就是說,在所有視窗排滿的情況下,每當有一個視窗的隊首客戶服務結束(結束時間相同的,視窗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將作為剛入隊的客戶的服務結束時間(即作為答案)儲存下來。

(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++解題報告: