我的PAT-ADVANCED代码仓:https://github.com/617076674/PAT-ADVANCED
原题链接:https://pintia.cn/problem-sets/994805342720868352/problems/994805493648703488
题目描述:
题目翻译:
1016 手机账单
长途电话公司按以下规则向客户收费:
拨打长途电话每分钟需要花费一定的金额,具体取决于拨打电话的时间。 当客户开始连接长途电话时,将记录时间,并且客户挂断电话的时间也将记录。 每月会向客户发送一张账单(按照当天的时间确定的费率)。 在给定一组电话记录的情况下,你的工作是为每个月准备账单。
输入格式:
每个输入文件包含一个测试用例。每个案例都有两部分:费率结构和电话记录。
费率结构包含由在一行的24个非负整数组成,分别表示从00:00到01:00的收费(美分/分钟),从01:00到02:00的收费……
下一行包含一个正数N(<= 1000),后跟N行记录。每个电话记录都包含客户名称(最多20个字符,不含空格),时间和日期(mm:dd:hh:mm),以及在线或离线字样。
对于每个测试用例,所有日期都在一个月内。每个在线记录与同一客户的按时间顺序排列的下一记录配对,前提是它是一个离线记录。任何未与离线记录配对的在线记录都将被忽略,离线记录也不会与在线记录配对。保证在输入中至少有一个呼叫配对良好。你可以假设同一客户的两条记录没有相同的时间。使用24小时制记录时间。
输出格式:
对每个测试用例,你必须为每个客户打印电话帐单。
票据必须按客户姓名的字母顺序打印。 对于每个客户,首先按照样例显示的格式在一行中打印客户名称和帐单月份。 然后,对于呼叫的每个时间段,在一行中打印开始和结束时间和日期(dd:hh:mm),持续时间(以分钟为单位)和呼叫费用。 必须按时间顺序列出呼叫。 最后,以样例显示的格式打印当月的总费用。
输入样例:
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
输出样例:
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
知识点:排序
思路一:为通话记录和账单记录各自建立一个结构体保存数据
注意点:
(1)题目给出的价格单位是美分/分钟,而我们需要输出的价格单位是美元,1美元 = 10美分。
(2)对通话记录和账单记录分别用两个map集合根据其姓名分组,再对每个姓名里的记录进行按时间排序。
(3)对于价格的计算,如果在线时间和离线时间不处在同一天,我们对每天进行分别计算,最后累加。比如题目中例子的aaa的账单记录中02:00:01 04:23:59,我们分3段计算其价格。第1段是02:00:01 - 02:24:00,第2段是03:00:00 - 03:24:00,第3段是04:00:00 - 04:23:59,这3段对于每段的开始时间和结束时间都是属于同一天的,其价格我们可以用同一个函数double calculatePriceSameDay(string a, string b)来计算。
(4)对于输出的顺序,我用了一个set集合保存所有的姓名,这时其姓名顺序就是题目要求的顺序,但set集合里的姓名不保证都有合格的通话记录,对于没有合格通话记录的姓名,我们不能输出。
时间复杂度是O(nlogn),其中n为某用户的最大记录数。空间复杂度是O(N)。
C++代码:
#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<algorithm>
#include<set>
using namespace std;
struct call { //通话记录
int month, day, hour, minute;
string description;
call(int _month, int _day, int _hour, int _minute, string _description) :
month(_month), day(_day), hour(_hour), minute(_minute), description(_description) {};
};
struct bill { //账单
int month1, day1, hour1, minute1;
int month2, day2, hour2, minute2;
int totalMinutes;
double price;
bill(int _month1, int _day1, int _hour1, int _minute1, int _month2, int _day2, int _hour2, int _minute2, int _totalMinutes, double _price) :
month1(_month1), day1(_day1), hour1(_hour1), minute1(_minute1), month2(_month2), day2(_day2), hour2(_hour2), minute2(_minute2),
totalMinutes(_totalMinutes), price(_price) {};
};
int price[24]; //每小时价格
int N; //通话记录数
map<string, vector<call> > callMap; //对通话记录按人名进行分类
map<string, vector<bill> > billMap; //对账单按人名进行分类
set<string> names; //记录人名
bool cmp(call a, call b); //对通话记录进行时间排序
int changeToMinutes(int day, int hour, int minute); //将时间转换为分钟
double calculatePriceSameDay(int hour1, int minute1, int hour2, int minute2); //获取同一天内的价格
double calculatePrice(int day1, int hour1, int minute1, int day2, int hour2, int minute2); //获取不同天的价格
int main() {
for(int i = 0; i < 24; i++) {
scanf("%d", &price[i]);
}
scanf("%d", &N);
string name, description;
int month, day, hour, minute;
for(int i = 0; i < N; i++) {
cin >> name;
scanf("%d:%d:%d:%d", &month, &day, &hour, &minute);
cin >> description;
callMap[name].push_back(call(month, day, hour, minute, description));
names.insert(name);
}
for(map<string, vector<call> >::iterator it = callMap.begin(); it != callMap.end(); it++) {
//对通话记录进行排序
sort(it->second.begin(), it->second.end(), cmp);
for(int i = 0; i < it->second.size(); i++) {
if(it->second[i].description.compare("on-line") == 0 && i + 1 < it->second.size()
&& it->second[i + 1].description.compare("off-line") == 0) {
//根据通话记录构建每个人的账单
call c1 = it->second[i], c2 = it->second[i + 1];
int beginTime = changeToMinutes(c1.day, c1.hour, c1.minute);
int endTime = changeToMinutes(c2.day, c2.hour, c2.minute);
int totalMinutes = endTime - beginTime;
double price = calculatePrice(c1.day, c1.hour, c1.minute, c2.day, c2.hour, c2.minute);
billMap[it->first].push_back(bill(c1.month, c1.day, c1.hour, c1.minute, c2.month, c2.day, c2.hour, c2.minute,
totalMinutes, price));
}
}
}
for(set<string>::iterator it = names.begin(); it != names.end(); it++) {
if(billMap[*it].size() == 0){ //如果没有通话记录,不要输出
continue;
}
cout << *it << " " ;
printf("%02d\n", callMap[*it][0].month);
double amount = 0;
for(int i = 0; i < billMap[*it].size(); i++) {
bill b = billMap[*it][i];
amount += b.price;
printf("%02d:%02d:%02d %02d:%02d:%02d %d $%.2f\n", b.day1, b.hour1, b.minute1, b.day2, b.hour2, b.minute2, b.totalMinutes, b.price);
}
printf("Total amount: $%.2f\n", amount);
}
return 0;
}
bool cmp(call a, call b) {
return changeToMinutes(a.day, a.hour, a.minute) < changeToMinutes(b.day, b.hour, b.minute);
}
int changeToMinutes(int day, int hour, int minute) {
return day * 24 * 60 + hour * 60 + minute;
}
double calculatePriceSameDay(int hour1, int minute1, int hour2, int minute2) {
double result = 0.0;
if(hour1 == hour2) {
result = price[hour1] * (minute2 - minute1) * 1.0 / 100;
} else {
result += price[hour1] * (60 - minute1);
for(int i = hour1 + 1; i < hour2; i++) {
result += price[i] * 60;
}
if(hour2 != 24) {
result += price[hour2] * minute2;
}
result /= 100;
}
return result;
}
double calculatePrice(int day1, int hour1, int minute1, int day2, int hour2, int minute2) {
if(day1 == day2) {
return calculatePriceSameDay(hour1, minute1, hour2, minute2);
}
double result = 0.0;
result += calculatePriceSameDay(hour1, minute1, 24, 0);
result += calculatePriceSameDay(0, 0, hour2, minute2);
int sum = 0;
for(int i = 0; i < 24; i++) {
sum += price[i];
}
result += (day2 - day1 - 1) * sum * 60 * 1.0 / 100;
return result;
}
C++解题报告:
思路二:先按人名排序,再按时间排序
(1)先以结构体类型Record存放单条记录的用户名、月、日、时、分以及通话状态(on-line还是off-line),然后根据题目需要对所有记录按下面的规则进行排序。
a:如果用户名不同,则按用户名字典序从小到大排序。
b:否则,如果月份不同,则按月份从小到大排序。
c:否则,如果日期不同,则按日期从小到大排序。
d:否则,如果小时不同,则按小时从小到大排序。
e:否则,按分钟从小到大排序。
(2)由于均已将所有记录先按姓名字典序排序,因此同一个用户的记录在数组中是连续且按时间顺序排列的。考虑到有效通话记录必须保证on-line和off-line在记录中先后出现,因此可以采用如下的方法确定该用户是否存在有效通话记录:设置int型变量needPrint表示该用户是否存在有效通话记录,初值为0。遍历该用户的所有所有记录,如果在needPrint为0的情况下遇到on-line的记录,就把needPrint置为1;如果在needPrint为1的情况下遇到off-line的记录,就把needPrint置为2。这样当遍历结束时,如果needPrint为2,则说明该用户存在有效通话记录。而为了下一步的书写方便,可以同时设置int型变量next,表示下一个用户的首记录在数组中的下标,并在遍历过程中对其进行累加,这样当遍历结束时,就得到next作为下一个用户的标识了。
(3)输出所有有效的通话记录。步骤2中已经分析过,有效通话记录必须保证on-line和off-line在记录中先后出现,这在数组中的含义是“on-line的数组下标与off-line的数组下标只相差1时才能进行配对”。也就是说,只要反复寻找当前下标i与其下一个元素的下标i + 1满足前者为on-line而后者off-line的记录即可。这样,找到的下标i和i + 1就是配对的有效通话记录,将它们输出即可。
(4)时长的计算方法:对已知的起始时间和终止时间,只需要不断将起始时间加1,判断其是否到达终止时间即可。
由于本思路中计算时长的方法选择了一分钟一分钟地递增,因此时间复杂度不好分析。空间复杂度是O(N)。
C++代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct Record{
char name[25]; //姓名
int month, dd, hh, mm; //月份、日、时、分
bool status; //status==true表示该记录为on-line,否则为off-line
};
const int maxn = 1010;
int toll[25]; //资费
Record rec[maxn], temp;
bool cmp(Record a, Record b);
void get_ans(int on, int off, int &time, int &money);
int main(){
for(int i = 0; i < 24; i++){
scanf("%d", &toll[i]); //资费
}
int n;
scanf("%d", &n); //记录数
char line[10]; //临时存放on-line或off-line的输入
for(int i = 0; i < n; i++){
scanf("%s %d:%d:%d:%d %s", rec[i].name, &rec[i].month, &rec[i].dd, &rec[i].hh, &rec[i].mm, line);
if(strcmp(line, "on-line") == 0){
rec[i].status = true; //如果是on-line,则令status为true
}else{
rec[i].status = false; //如果是off-line,则令status为false
}
}
sort(rec, rec + n, cmp); //排序
int on = 0, off, next; //on和off为配对的两条记录,next为下一个用户
while(on < n){ //每次循环处理单个用户的所有记录
int needPrint = 0; //needPrint表示该用户是否需要输出
next = on; //从当前位置开始寻找下一个用户
while(next < n && strcmp(rec[next].name, rec[on].name) == 0){
if(needPrint == 0 && rec[next].status){
needPrint = 1; //找到on,置needPrint为1
}else if(needPrint == 1 && !rec[next].status){
needPrint = 2; //在on之后如果找到off,置needPrint为2
}
next++; //next自增,直到找到不同名字,即下一个用户
}
if(needPrint < 2){ //没有找到配对的on-off
on = next;
continue;
}
int AllMoney = 0; //总共花费的钱
printf("%s %02d\n", rec[on].name, rec[on].month);
while(on < next){
while(on < next - 1 && !(rec[on].status && !rec[on + 1].status)){
on++; //直到找到连续的on-line和off-line
}
off = on + 1; //off必须是on的下一个
if(off == next){ //已经输出完毕所有配对的on-line和off-line
on = next;
break;
}
printf("%02d:%02d:%02d ", rec[on].dd, rec[on].hh, rec[on].mm);
printf("%02d:%02d:%02d ", rec[off].dd, rec[off].hh, rec[off].mm);
int time = 0, money = 0; //时间、单次记录花费的钱
get_ans(on, off, time, money); //计算on到off内的时间和金钱
AllMoney += money;
printf("%d $%.2f\n", time, money / 100.0);
on = off + 1; //完成一个配对,从off + 1开始找下一对
}
printf("Total amount: $%.2f\n", AllMoney / 100.0);
}
return 0;
}
bool cmp(Record a, Record b){
int s = strcmp(a.name, b.name);
if(s != 0){
return s < 0; //优先按姓名字典序从小到大排序
}else if(a.month != b.month){
return a.month < b.month; //按月份从小到大排序
}else if(a.dd != b.dd){
return a.dd < b.dd; //按日期从小到大排序
}else if(a.hh != b.hh){
return a.hh < b.hh; //按小时从小到大排序
}else{
return a.mm < b.mm; //按分钟从小到大排序
}
}
void get_ans(int on, int off, int &time, int &money){
temp = rec[on];
while(temp.dd < rec[off].dd || temp.hh < rec[off].hh || temp.mm < rec[off].mm){
time++; //该次记录总时间加1min
money += toll[temp.hh]; //花费增加toll[temp.hh]
temp.mm++; //当前时间加1min
if(temp.mm >= 60){ //当前分钟数到达60
temp.mm = 0; //进入下一个小时
temp.hh++;
}
if(temp.hh >= 24){ //当前小时数到达24
temp.hh = 0; //进入下一天
temp.dd++;
}
}
}
C++解题报告: