PAT-ADVANCED1026——Table Tennis
題目描述:
題目翻譯:
1026 乒乓球桌
一個乒乓球俱樂部有N張乒乓球桌對外開放。乒乓球桌編號為1 ~ N。對任何一對運動員,如果他們到達時有空餘的乒乓球桌,他們會被分配到可用的編號最小的那張乒乓球桌。如果所有的桌子都被佔了,他們必須在佇列中等待。假設任何一對運動員最多隻能佔用球桌2小時。
你的任務是為每個人計算他們在佇列中的等待時間,以及每張球桌一天服務的運動員數量。
由於俱樂部為他們的VIP會員保留了一些VIP球桌,這使得這件事情變得稍微複雜了些。當一張VIP球桌對外開放時,佇列中的第一對VIP運動員將優先享有這張球桌的使用權。然而,如果佇列中沒有VIP運動員,下一對運動員可以使用這張球桌。另外,如果輪到了VIP運動員,但是沒有任何VIP球桌是可用的,他們就會像普通運動員一樣被安排。
輸入格式:
每個輸入檔案包含一個測試用例。對每個測試用例,第一行包含一個整數N(<= 10000)——代表運動員總對數。接下來的N行,每行包含2個時間和一個VIP標記:HH:MM:SS——代表這對運動員的到達時間,P——代表這對運動員的運動時間,以及tag——如果是1表示是VIP運動員,是0則不是VIP運動員。題目保證到達時間在俱樂部的開放時間08:00:00 ~ 21:00:00內。假設沒有任何兩對運動員在同一時間到達。緊跟著運動員資訊的是2個正整數:K(<= 100)——代表球桌數量,以及M(< K)——代表VIP球桌數量。最後一行包含M個VIP球桌編號。
輸出格式:
對每個測試用例,首先為每對運動員打印出到達時間,開始時間以及等待時間,樣式和樣例中一致。接著在一行中打印出每張乒乓球桌一天服務的運動員數量。注意到輸出必須按照開始時間的増序排列。等待時間必須換算成一個整數表示的分鐘值。如果在關門之前,某對運動員無法得到一張乒乓球桌,他們的資訊不應該被打印出來。
輸入樣例:
9
20:52:00 10 0
08:00:00 20 0
08:02:00 30 0
20:51:00 10 0
08:10:00 5 0
08:12:00 10 1
20:50:00 10 0
08:01:30 15 1
20:53:00 10 1
3 1
2
輸出樣例:
08:00:00 08:00:00 0
08:01:30 08:01:30 0
08:02:00 08:02:00 0
08:12:00 08:16:30 5
08:10:00 08:20:00 10
20:50:00 20:50:00 0
20:51:00 20:51:00 0
20:52:00 20:52:00 0
3 3 2
知識點:排序
(1)題目中最醒目的就是球員和球桌,因此需要分別對它們建立結構體。
對球員來說,需要知道到達時間、訓練開始時間、訓練時長、是否是VIP球員。
對球桌來說,需要知道當前佔用該球桌的球員的訓練結束時間
(2)由於球員的到達時間是無序的,因此不能直接定義佇列,需要使用vector存放讀入的新球員的資訊,然後再將其按到達時間排序,才能進行下面的處理。而由於球桌的編號是已知的(1 ~ K),因此不妨直接定義一個Table型的陣列table[K],用來存放所有球桌的資訊。
(3)按佇列中的球員順序來分配球桌,因此需要對每個待分配的隊首球員,求當前最早結束訓練的球桌編號(記為idx)。考慮到有VIP球桌的設定,因此可以根據最早空閒的球桌idx是否是VIP球桌進行分類,得到如下的思路:
a:如果最早空閒的球桌idx是VIP球桌,且隊首球員是VIP球員,那麼把球桌idx分配給他。
b:如果最早空閒的球桌idx是VIP球桌,且隊首球員不是VIP球員,那麼檢視第一個VIP球員是否在球桌idx空閒之前到達。如果是,就讓該VIP球員插隊,把球桌idx分配給該VIP球員;否則,還是把球桌分配給隊首球員。
c:如果最早空閒的球桌idx不是VIP球桌,且隊首球員不是VIP球員,那麼把球桌idx分配給他。
d:如果最早空閒的球桌idx不是VIP球桌,且隊首球員是VIP球員,那麼查詢最早空閒的VIP球桌VIPidx,看其是否在該VIP球員到達之前空閒。如果是,就把球桌VIPidx分配給該VIP球員;否則,還是把球桌idx分配給該VIP球員。
(4)為了實現上面的過程,這裡可以設定下標i與VIPi,分別指向當前隊首球員(所有還未訓練的球員的隊首)和當前隊首VIP球員。之後,每次在把球桌分配給VIP球員時,就要把VIPi指向下一個VIP球員。需要注意的是,由於存在VIP球員插隊的情況導致i < VIPi的發生,這樣當下一層i掃描到VIPi時,就需要跳過這個VIP球員。處理辦法是:如果當前隊首球員是VIP球員,且滿足i < VIPi,就直接令i++,跳過當前的分配過程。
注意點如下:
(1)如果存在空閒的VIP球桌,則VIP球員總是優先選擇編號最小的空閒VIP球桌而不是編號最小的空閒普通球桌。
(2)要求每個球員必須在2h內結束訓練,超過2h的壓縮為2h。
(3)等待時長應嚴格四捨五入,即30s應當進位至1min。
(4)可能有不再21:00:00前到達的球員,對此直接不予考慮。
(5)分配球桌時要根據球桌空閒時間和球員到達時間的先後來選擇該球桌的下一次結束時間。
(6)在最早空閒的球桌idx是VIP球桌,且隊首球員不是VIP球員的情況下,如果第一個VIP球員的VIPi比球桌空閒時間來得早,那麼將把球桌分配給VIPi。要注意的是,此時隊首球員是不變的。
(7)VIPi不能從0開始,必須在一開始就指向第一個VIP球員。
(8)球桌編號是從1開始的。
時間複雜度是O(NlogN)。空間複雜度是O(N)。
C++程式碼:
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
struct Player{
int arriveTime, startTime, trainTime; //到達時間、訓練開始時間及訓練時長
bool isVIP; //是否是VIP球員
};
struct Table{
int endTime, numServe; //當前佔用該球桌的球員的結束時間及已訓練的人數
bool isVIP; //是否是VIP球桌
};
const int K = 111; //球桌數
const int INF = 1000000000; //無窮大數
Player newPlayer; //臨時存放新讀入的球員
Table table[K]; //K個球桌
vector<Player> player; //球員佇列
int convertTime(int h, int m, int s); //將時間轉換為以s為單位,方便比較和計算
bool cmpArriveTime(Player a, Player b); //按到達時間排序
bool cmpStartTime(Player a, Player b); //按開始時間排序
int nextVIPPlayer(int VIPi); //編號VIPi從當前VIP球員移到下一個VIP球員
void allotTable(int pID, int tID); //將編號為tID的球桌分配給編號為PID的球員
int main(){
int n, k, m, VIPtable;
scanf("%d", &n); //球員數
int stTime = convertTime(8, 0, 0); //開門時間為8點
int edTime = convertTime(21, 0, 0); //關門時間為21點
for(int i = 0; i < n; i++){
int h, m, s, trainTime, isVIP; //時、分、秒、訓練時長、是否是VIP球員
scanf("%d:%d:%d %d %d", &h, &m, &s, &trainTime, &isVIP);
newPlayer.arriveTime = convertTime(h, m, s); //到達時間
newPlayer.startTime = edTime; //開始時間初始化為21點
if(newPlayer.arriveTime >= edTime){ //21點及以後的直接排除
continue;
}
newPlayer.trainTime = trainTime <= 120 ? trainTime * 60 : 7200; //訓練時長
newPlayer.isVIP = isVIP; //是否是VIP
player.push_back(newPlayer); //將newPlayer加入到球員佇列中
}
scanf("%d%d", &k, &m); //球桌數及VIP球桌數
for(int i = 1; i <= k; i++){
table[i].endTime = stTime; //當前訓練結束時間為8點
table[i].numServe = table[i].isVIP = 0; //初始化numServe與isVIP
}
for(int i = 0; i < m; i++){
scanf("%d", &VIPtable); //VIP球桌編號
table[VIPtable].isVIP = 1; //記為VIP球桌
}
sort(player.begin(), player.end(), cmpArriveTime); //按到達時間排序
int i = 0, VIPi = -1; //i用來掃描所有球員,VIPi總是指向當前最前的VIP球員
VIPi = nextVIPPlayer(VIPi); //找到第一個VIP球員的編號
while(i < player.size()){ //當前佇列最前面的球員為i
int idx = -1, minEndTime = INF; //尋找最早能空閒的球桌
for(int j = 1; j <= k; j++){
if(table[j].endTime < minEndTime){
minEndTime = table[j].endTime;
idx = j;
}
}
//idx為最早空閒的球桌編號
if(table[idx].endTime >= edTime){ //已經關門,直接break
break;
}
if(player[i].isVIP && i < VIPi){
i++; //如果i號是VIP球員,但是VIPi > i,說明i號球員已經在訓練
continue;
}
//以下按球桌是否是VIP、球員是否是VIP,進行4種情況討論
if(table[idx].isVIP){
if(player[i].isVIP){ //第一種情況:球桌是VIP,球員是VIP
allotTable(i, idx); //將球桌idx分配給球員i
if(VIPi == i){
VIPi = nextVIPPlayer(VIPi); //找到下一個VIP球員
}
i++;
}else{ //第二種情況:球桌是VIP,球員不是VIP
//如果當前隊首的VIP球員比該VIP球桌早,就把球桌idx分配給他
if(VIPi < player.size() && player[VIPi].arriveTime <= table[idx].endTime){
allotTable(VIPi, idx); //將球桌idx分配給球員VIPi
VIPi = nextVIPPlayer(VIPi); //找到下一個VIP球員
}else{
//隊首VIP球員比該VIP球桌遲,仍然把球桌idx分配給球員i
allotTable(i, idx); //將球桌idx分配給球員i
i++; //i號球員開始訓練,因此繼續佇列的下一個人
}
}
}else{
if(!player[i].isVIP){ //第三種情況:球桌不是VIP,球員不是VIP
allotTable(i, idx); //將球桌idx分配給球員i
i++; //i號球員開始訓練,因此繼續佇列的下一個人
}else{ //第四種情況:球桌不是VIP,球員是VIP
//找到最早空閒的VIP球桌
int VIPidx = -1, minVIPEndTime = INF;
for(int j = 1; j <= k; j++){
if(table[j].isVIP && table[j].endTime < minVIPEndTime){
minVIPEndTime = table[j].endTime;
VIPidx = j;
}
}
//最早空閒的VIP球桌編號是VIPidx
if(VIPidx != -1 && player[i].arriveTime >= table[VIPidx].endTime){
//如果VIP球桌存在,且空閒時間比球員來的時間早
//就把它分配給球員i
allotTable(i, VIPidx);
if(VIPi == i){
VIPi = nextVIPPlayer(VIPi); //找到下一個VIP球員
}
i++;
}else{
//如果球員來時VIP球桌還未空閒,就把球桌idx分配給他
allotTable(i, idx);
if(VIPi == i){
VIPi = nextVIPPlayer(VIPi); //找到下一個VIP球員
}
i++;
}
}
}
}
sort(player.begin(), player.end(), cmpStartTime); //按開始時間排序
for(int i = 0; i < player.size() && player[i].startTime < edTime; i++){
int t1 = player[i].arriveTime;
int t2 = player[i].startTime;
printf("%02d:%02d:%02d ", t1 / 3600, t1 % 3600 / 60, t1 % 60);
printf("%02d:%02d:%02d ", t2 / 3600, t2 % 3600 / 60, t2 % 60);
printf("%.0f\n", round((t2 - t1) / 60.0));
}
for(int i = 1; i <= k; i++){
printf("%d", table[i].numServe);
if(i < k){
printf(" ");
}
}
return 0;
}
int convertTime(int h, int m, int s){
return h * 3600 + m * 60 + s;
}
bool cmpArriveTime(Player a, Player b){
return a.arriveTime < b.arriveTime;
}
bool cmpStartTime(Player a, Player b){
return a.startTime < b.startTime;
}
int nextVIPPlayer(int VIPi){
VIPi++; //先將VIPi加1
while(VIPi < player.size() && !player[VIPi].isVIP){
VIPi++; //只要當前球員不是VIP,就讓VIPi後移一位
}
return VIPi; //返回下一個VIP球員的ID
}
void allotTable(int pID, int tID){
if(player[pID].arriveTime <= table[tID].endTime){ //更新球員的開始時間
player[pID].startTime = table[tID].endTime;
}else{
player[pID].startTime = player[pID].arriveTime;
}
//該球桌的訓練結束時間更新為新球員的結束時間,並讓服務人數加1
table[tID].endTime = player[pID].startTime + player[pID].trainTime;
table[tID].numServe++;
}
C++解題報告: