1. 程式人生 > >甲級PAT 1016 Phone Bills(這是到目前為止甲級寫的最長最複雜的程式碼,其實並不難,邏輯理順了就好)

甲級PAT 1016 Phone Bills(這是到目前為止甲級寫的最長最複雜的程式碼,其實並不難,邏輯理順了就好)

1016 Phone Bills(25 分)

A long-distance telephone company charges its customers by the following rules:

Making a long-distance call costs a certain amount per minute, depending on the time of day when the call is made. When a customer starts connecting a long-distance call, the time will be recorded, and so will be the time when the customer hangs up the phone. Every calendar month, a bill is sent to the customer for each minute called (at a rate determined by the time of day). Your job is to prepare the bills for each month, given a set of phone call records.

Input Specification:

Each input file contains one test case. Each case has two parts: the rate structure, and the phone call records.

The rate structure consists of a line with 24 non-negative integers denoting the toll (cents/minute) from 00:00 - 01:00, the toll from 01:00 - 02:00, and so on for each hour in the day.

The next line contains a positive number N (≤1000), followed by N lines of records. Each phone call record consists of the name of the customer (string of up to 20 characters without space), the time and date (mm:dd:hh:mm), and the word on-line or off-line.

For each test case, all dates will be within a single month. Each on-line

 record is paired with the chronologically next record for the same customer provided it is an off-line record. Any on-line records that are not paired with an off-line record are ignored, as are off-line records not paired with an on-line record. It is guaranteed that at least one call is well paired in the input. You may assume that no two records for the same customer have the same time. Times are recorded using a 24-hour clock.

Output Specification:

For each test case, you must print a phone bill for each customer.

Bills must be printed in alphabetical order of customers' names. For each customer, first print in a line the name of the customer and the month of the bill in the format shown by the sample. Then for each time period of a call, print in one line the beginning and ending time and date (dd:hh:mm), the lasting time (in minute) and the charge of the call. The calls must be listed in chronological order. Finally, print the total charge for the month in the format shown by the sample.

Sample Input:

10 10 10 10 10 10 20 20 20 15 15 15 15 15 15 15 20 30 20 15 15 10 10 10
10
CYLL 01:01:06:01 on-line
CYLL 01:28:16:05 off-line
CYJJ 01:01:07:00 off-line
CYLL 01:01:08:03 off-line
CYJJ 01:01:05:59 on-line
aaa 01:01:01:03 on-line
aaa 01:02:00:01 on-line
CYLL 01:28:15:41 on-line
aaa 01:05:02:24 on-line
aaa 01:04:23:59 off-line

Sample Output:

CYJJ 01
01:05:59 01:07:00 61 $12.10
Total amount: $12.10
CYLL 01
01:06:01 01:08:03 122 $24.40
28:15:41 28:16:05 24 $3.85
Total amount: $28.25
aaa 01
02:00:01 04:23:59 4318 $638.80
Total amount: $638.80

一開始要寫這題解析我是很崩潰的。。。尤其是剛寫完這麼長的程式碼。壓根不想重新看吶(拍桌) ,光寫完這題就花了三個小時。。寫部落格又花了兩小時。。這要是pat考試怕是已經涼了

題目要求:

 這是一道和生活息息相關的題目。一家長途電話收費公司需要根據所有的通話記錄列印每位客戶的賬單。每項通話記錄中包含使用者的姓名、通話記錄時間,通話記錄狀態(可以理解為on-line是在打電話,off-line是在掛電話)。通話記錄時間對應mm:dd:hh:mm分別表示月份、日期、小時、分鐘。需要找到同一個使用者按時間順序匹配的通話記錄(即同一使用者距離最近的on-line和off-line,這裡可以看示例給的aaa使用者,第一個on-line時間應該是01:01:03,而最後賬單列印的是02:00:0,所以是在off-line前的最後一個on-line,其他沒有配對的忽視掉)計算這些能匹配通話記錄對應的持續時間、花費以及某個使用者所有通話記錄的總花費。

公司的收費是根據每個時間段一天24個時間段。每個時間對應不同的收費標準,第一行給出時間段00:00-01:00 、01:00-02:00、02:00-03:00……23:00-00:00這24個時間段對應每分鐘多少美分收費標準。所以每次通話記錄的收費都要按時間段來計算

解題思路:

首先想好怎麼儲存這些資料以及輸出的資料。題目中一共有兩張表,一個是所有通話記錄的表,還有一個是列印輸出的表,這兩個中的內容不一樣,所以利用兩個結構體Call和Bill(用一維陣列儲存多個人資訊)分別儲存,Call中包含使用者的姓名、通話記錄時間,通話記錄狀態,Bill包含使用者姓名、通話月份、通話詳情資訊(這個再用一個結構體DetailBill儲存)、總花費、通話詳情資訊的個數(因為每個通話詳情資訊個數可能不一樣,需要標註)。還要明確需要寫幾個函式:兩種排序(一個通話記錄按時間排序,一個總賬單按姓名排序)、查詢匹配off-line、計算持續時間、計算花費

然後就是開始慢慢解題。。。這裡首先將所有的通話記錄按時間從小到大進行排序,這個可以利用重寫sort排序的比較函式。排完序後從前往後找當前通話記錄狀態為on-line的使用者對應的off-line,因為這裡要找離off-line最近的on-line,若在查詢過程中發現有該使用者的通話記錄的on-line則當前通話記錄匹配失敗,繼續下一個通話記錄。

對於匹配成功的接著進行下面的運算,這裡on-line下標i,off-line下標matchi。首先查詢該使用者姓名是否在總表bill中,如果不在新存入表中,若在則增加當前表中的通話詳情。然後就是把各種資訊計算儲存到總表賬單中。

注意:

1.在計算持續時間以及花費的時候先將每個位數轉化為對應的Int型方便計算。

2.計算持續時間要注意分鐘、小時、天數的轉換。例如22:18:17和23:06:16。分鐘的計算不是直接把分鐘大的減去分鐘小的,而是把總時間大的減去總時間小的,這裡肯定是第二個總時間大,23號>22號。所以分鐘應是16向前06借60,為76。利用76-17 = 59分鐘。此時第二個時間小時為05,05<18所以要向天數借1,也就是05+24 =29,利用29-18=11小時。可以自己推算一下這兩個時間差正好是11小時59分

3.計算花費要注意和時間段進行匹配。 因為有的時間差可能相差幾天這樣,先將天數的差乘以24全部轉化為小時的時間差。

接下來計算時間差有兩種情況,一個是兩個小時相等,假設時間分別是02:06:06和02:06:25,只要減分鐘就可以了。

另一種小時不相等,比如03:06:06和04:06:25,首先將04:06:25轉化為03:30:25。第一個時間段計費06:06-07:00,第二個時間段07:00-08:00……最後一個時間段06:00-06:25,所以只要處理第一次把60-06=54,除了最後一次剩下的全部是60-0。 每次計算完時間段的花費,時間段向後挪一個就好了,將大於等於24小時轉化為時間段只需要對24取餘就可以了,這樣24對應0,25對應1…… 。這裡要注意迴圈條件不能寫成for(i=0;i<=calldatetime[n][2]-calldatetime[m][2];i++) 而要寫成j = calldatetime[n][2]-calldatetime[m][2]; for(i=0;i<=j;i++){},不然的話就會少執行迴圈次數,出現錯誤。

4.姓名從小大大排序,按字母順序 這裡就按ASCII碼排序就可以了,'a' 97>'A' 65,所以大寫字母都排在前面,當然對於aa和aaa當然是aa排在前面,aaa排在後面。

5.在計算花費是根據分鐘和對應每分鐘多少美分計算,最後要求的單位是美元,所以要將美分轉化為美元。。1美元=100美分

6.做這種題一定要耐心+細心,少稍不注意就有錯的地方

完整程式碼:

#include<iostream>
#include<string>
#include<sstream>
#include<cstring> 
#include<iomanip>
#include<algorithm>
using namespace std;
#define maxsize 1001

//所有的通話資訊 
typedef struct call{
	string name; //姓名 
	string datetime; //電話日期 
	string state; //on-line or off-line 
}Call;

//每個使用者詳細的單次通話資訊 
typedef struct DetailRecord{
	string starttime; //開始時間 
	string endtime; //結束時間 
	int lastingtime; //持續時間 
	float cost; //單次通話花費 
}DetailRecord;

//每個使用者的賬單 
typedef struct Bill{
	string name; //姓名 
	string month; //月份 
	DetailRecord record[maxsize/2]; //詳細賬單列表 
	float totalcost; //總花費 
	int recordnum; //記錄每個人的詳細賬單個數 
}Bill;

int price[25];  //每個時間段花費 
Call call[maxsize]; //儲存所有的通話資訊 
Bill bill[maxsize/2]; //儲存所有使用者的賬單資訊
int billnum = 0; //記錄bill個數,從0開始儲存  
int N;
int calldatetime[maxsize][4]; //每個通話時間儲存為int, 分別對應月份,日期,小時,分鐘

//將所有通話資訊按時間從小到大排序 
bool comp(Call call1,Call call2){
	int intarray[8]; //每位分別對應 call1 的月份,日期,小時,分鐘call2的月份,日期,小時,分鐘;
	stringstream ss;
	int i,j = 0;
	for(i=0;i<8;i++){
		if(i==4) j=0;
		if(i<4){
			ss << call1.datetime.substr(j,j+2);
			ss >> intarray[i];
		}else{
			ss << call2.datetime.substr(j,j+2);
			ss >> intarray[i];		
		}
		ss.clear();
		ss.str("");
		j+=3;	
	}
	if(intarray[0]<intarray[4]) return true; //比較月份 
	else if(intarray[0] == intarray[4]){
		if(intarray[1]<intarray[5]) return true; //比較日期 
		else if(intarray[1] == intarray[5]){
			if(intarray[2]<intarray[6]) return true; //比較小時
			else if(intarray[2] == intarray[6]){
				if(intarray[3]<intarray[7]) return true; //比較分鐘 
				else return false;		
			}
			else return false;	
		}
		else return false;
	}
	else return false;
}

/*找到第x通話記錄對應的同一使用者與on-line記錄最近匹配的off-line,
若匹配到返回對應在所有通話資訊的下標,
反之返回-1,
若第x通話記錄後再次出現同一使用者on-line,也返回-1 */
int findmatch(int x){
	int i;
	for(i=x+1;i<N;i++){
		if(call[i].name==call[x].name){
			if(call[i].state=="off-line") return i;
			else return -1;
		}
	}
	if(i == N) return -1;
}

//將第i個通話記錄對應的月份,日期,小時,分鐘轉化為整型calldatetime[i][0],calldatetime[i][1],calldatetime[i][2],calldatetime[i][3]
void trans(int i){
	int k,j=0;
	stringstream ss;
	for(k=0;k<4;k++){
		ss << call[i].datetime.substr(j,j+2);
		ss >> calldatetime[i][k];
		ss.clear();
		ss.str("");
		j+=3;
	}
} 

//計算第m個通話記錄與第n個通話記錄的持續時間 (這個計算大家先自己算一下就能理解了) 
int countlastingtime(int m,int n){
	int lastingtime=0;
	trans(m);
	trans(n);
	if(calldatetime[n][3] > calldatetime[m][3]){ //比較分鐘 
		lastingtime = lastingtime + calldatetime[n][3] - calldatetime[m][3]; 
	}else if(calldatetime[n][3] < calldatetime[m][3]){ //若off-line的分鐘要小於on-line的分鐘則向小時借60分鐘
		calldatetime[n][2]--;
		calldatetime[n][3]+=60;
		lastingtime = lastingtime + calldatetime[n][3] - calldatetime[m][3];
	}
	if(calldatetime[n][2] > calldatetime[m][2]){ //比較小時 
		lastingtime = lastingtime + (calldatetime[n][2] - calldatetime[m][2])*60; 
	}else if(calldatetime[n][2] < calldatetime[m][2]){
		calldatetime[n][1]--;
		calldatetime[n][2]+=24;
		lastingtime = lastingtime + (calldatetime[n][2] - calldatetime[m][2])*60;
	}
	if(calldatetime[n][1] > calldatetime[m][1]){ //比較日期 
		lastingtime = lastingtime + (calldatetime[n][1] - calldatetime[m][1])*24*60;
	}
	return lastingtime;
}

//計算第m個通話記錄與第n個通話記錄的花費 (注意算的時候是美分,返回為美元,1美元=100美分 
float countcost(int m,int n){
	int i,j,timesection;
	float cost = 0;
	trans(m);
	trans(n);
	calldatetime[n][2] += (calldatetime[n][1] - calldatetime[m][1])*24;
	j = calldatetime[n][2]-calldatetime[m][2];
	for(i=0;i<=j;i++){
		if(calldatetime[n][2] == calldatetime[m][2]){
			timesection = calldatetime[m][2]%24;
			cost += price[timesection] * (calldatetime[n][3] - calldatetime[m][3]);
		}else{
			timesection = calldatetime[m][2]%24;
			cost += price[timesection] * (60 - calldatetime[m][3]);
			calldatetime[m][3] = 0;
			calldatetime[m][2]++;
		}
	}
	cost = cost/100.0;
	return cost;
}

//將最後的總賬單按字母順序排序 
bool compa(Bill bill1,Bill bill2){
	string name1 = bill1.name;
	string name2 = bill2.name;
	int i,j;
	for(i=0;i<name1.length();i++){
		if(i==name2.length()) return false;
 		if(int(name1[i])<int(name2[i])) return true;
		else if(int(name1[i])>int(name2[i])) return false;
	}
	if(i==name1.length()) return true;
}

int main(){
	ios::sync_with_stdio(false);
	int i,j,matchi,recordnum;
	float sum;
	string name; 
	for(i=0;i<24;i++){
		cin>>price[i];
	}
	cin>>N;
	for(i=0;i<N;i++){
		cin>>call[i].name>>call[i].datetime>>call[i].state;
	}
	sort(call,call+N,comp);
	for(i=0;i<N;i++){
		if(call[i].state=="on-line"){
			matchi = findmatch(i);
			if(matchi > 0){
				name = call[i].name;
				//查詢該使用者是否在賬單總表裡 
				for(j=0;j<billnum;j++){
					if(bill[j].name == name) break;
				}
				if(j==billnum){ //不在賬單總表加入 
					bill[j].name = name;
					bill[j].month = call[i].datetime.substr(0,2);
					bill[j].record[0].starttime = call[i].datetime.substr(3,11);
					bill[j].record[0].endtime = call[matchi].datetime.substr(3,11);
					bill[j].record[0].lastingtime = countlastingtime(i,matchi);
					bill[j].record[0].cost = countcost(i,matchi);
					bill[j].recordnum = 1;
					billnum++;	
				}else{		
					recordnum = bill[j].recordnum;
					bill[j].record[recordnum].starttime = call[i].datetime.substr(3,11);
					bill[j].record[recordnum].endtime = call[matchi].datetime.substr(3,11);
					bill[j].record[recordnum].lastingtime = countlastingtime(i,matchi);
					bill[j].record[recordnum].cost = countcost(i,matchi);
					bill[j].recordnum++;
				}
			}
		}
	}
	sort(bill,bill+billnum,compa);
	for(i=0;i<billnum;i++){
		sum = 0;
		cout<<bill[i].name<<" "<<bill[i].month<<endl;
		for(j=0;j<bill[i].recordnum;j++){
			cout<<bill[i].record[j].starttime<<" "<<bill[i].record[j].endtime<<" "<<bill[i].record[j].lastingtime<<" $"<<fixed<<setprecision(2)<<bill[i].record[j].cost<<endl;
			sum += bill[i].record[j].cost;
		}
		cout<<"Total amount: $"<<fixed<<setprecision(2)<<sum<<endl;
	} 
	return 0;
} 

小技巧:

1.將字串轉化為int陣列用stringstream,在同一個函式用多次需要清空一下。ss.clear();ss.str("")

2.對字串擷取用substr(0,2)是擷取第0位和第1位,第二位不算