PAT 1016 Phone Bills (25分) sort() + map 逻辑较为复杂

Scroll Down

题目

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

题目大意

我这撇脚的英语 好几次把题目看成 “给 Billls 打电话”,哈哈哈,正确意思是“电话账单”

长途电话根据打电话时刻的不同具有不同的收费标准,每个小时内的收费是相同的,一天24小时(编号0-23)各个小时收费不一样。

注意给出的账单是一个月内的

第一行给出 0-2324个小时 每个小时内的收费(多少/每分钟

然后给出 N 个人的电话记录(姓名 月:日:时:分 on/off

一次有效的通话记录是:同一个人,先有一个on记录,紧接着一个off记录(按时间先后顺序)。单独的on没有off,或以off开始的记录都被认为是无效记录。

要求输出 给出的所有记录中,按照名字的先后顺序,每个人所有有效的通话记录(按照时间顺序)及每次的花费,和所有通话的总花费。

思路分析

  • 首先创建结构体 Record 保存每一条记录信息,由于每条记录的时间都是 月:日:时:分,又因为所有记录都是同一个月的,所以我们把时间都转成 从本月00:00开始的分钟数
	struct Record {
	    string name; // 用户名
	    // 打电话的时间,status:online:1/offline:0,一个online后一个offline配对,一条有效的记录
	    int month, day, hour, minute, status;
	    int time; // 为了计算两次通话的时间差,计算出每条记录开始时刻对应的从每月0号00:00对应的分钟数,之后做差即可得到本次通话时长
	};
  • 题目给出了一天24个小时每个小时内每分钟的收费(rate[0]-rate[23]),那如果这个人一整天都在通话呢?我们引入一个“第25小时”或者说是叫“全小时”的概念,表示一整天通话,那么在这个小时内 每分钟的费用就是 前24小时各个小时内资费的和,用rate[24]存储。(就相当于每个小时的每一分钟都在通话,那么一分钟的花费不就是每个小时每分钟的花费的和?)

  • 结合 1 和 2,我们能比较方便的计算出 一次通话的费用。首先:我们把每个记录都转成了从0号00:00开始的分钟数,先不考虑有效无效记录,我就可以认为每一条记录都是从0号00:00开始通话到这个记录的时刻,我就可以求出每个记录从0号00:00开始的花费,如果计算花费,两条记录直接作差即可

    这两个地方比较抽象,但是这样就都能转成以分钟为单位统一运算;如果你觉得不好理解,就从第一天开始计算每一天的费用,累加到现在吧,也可以。

	// 从0号0时0分通话到现在
	double BillFromZero(Record rec) {
	    // 首先经过了这么多天,也就是 天数 个 “全小时”,每分钟费用是 rate[24]
	    double total = rec.day * 60 * rate[24];
	    // 然后最后一天,持续到 rec.hour 小时,每个小时内,每分钟费用是 rate[rec.hour] 
	    for (int i = 0; i < rec.hour; ++i)
	        total += 60 * rate[i];
	    // 最后一天,rec.hour 小时内,持续了 rec.minute分钟
	    total += rec.minute * rate[rec.hour];
	    // 计算出的结果是 分,要转成 元
	    return total / 100.0;
	}
  • 接下来,我们要对所有记录进行排序,比较大小函数cmp怎么实现呢?首先我们的账单要按名字排序,然后对于同一个人它所有的记录要按时间顺序排序。
	bool cmp(Record a, Record b) {
		// 每个记录的开始时间都转成了0号00:00开始的分钟数,保存在time
	    return a.name != b.name ? a.name < b.name : a.time < b.time;
	}
  • 排序完了之后,我们就要从所有记录中筛选出每个用户的全部有效记录,所以这里需要一个map<string, vector<Record>> customer_record 来保存;键是 姓名;值是 他所有有效通话记录

    怎么筛选呢?
    对于排好序的全部记录,如果是有效记录,肯定是一个on记录紧跟一个off记录,并且是同一个人。

	    for (int i = 1; i < n; ++i) {
	        // 同一个人,一个on紧跟着一个off
	        if (record[i].name == record[i - 1].name && record[i].status == 0 && record[i - 1].status == 1){
	            // 保存这个人这一次有效的通话记录
	            customer_record[record[i].name].push_back(record[i - 1]);
	            customer_record[record[i].name].push_back(record[i]);
	        }
	    }
  • 之后就是输出结果了,对于 map 中的每个<K, V>K 是姓名,V是这个人全部有效记录V连续两个记录组成一条有效记录

    以输出一个人的记录为例,my_record 是他的 name 对应的 Vvector<Record>

   for (int i = 1; i < my_record.size(); i += 2) {
          // 本次通话的账单
          double temp_bill = BillFromZero(my_record[i]) - BillFromZero(my_record[i - 1]);
          // 打电话天:时:分 挂电话:时:分 时长 花费
          printf("%02d:%02d:%02d %02d:%02d:%02d %d $%.2f\n", 
          		my_record[i - 1].day,my_record[i - 1].hour, my_record[i - 1].minute, 
          		my_record[i].day, my_record[i].hour, my_record[i].minute,                                          		 my_record[i].time - my_record[i - 1].time, temp_bill);
          // 总账单加上本次账单	
          bill += temp_bill;
      }

完整代码

注意输入输出格式 空格、位数、小数点后保留 等细节问题。

/*
 * @Author: wang wei
 * @Date: 2020-05-08 20:41:15
 * @LastEditTime: 2020-05-11 13:36:24
 * @Description:  
 */
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;

/**
 * 一条记录 
 */
struct Record {
    string name; // 用户名
    // 打电话的时间,status:online:1/offline:0,一个online后一个offline配对,一条有效的记录
    int month, day, hour, minute, status;
    int time; // 为了计算两次通话的时间差,计算出每条记录开始时刻对应的从每月0号00:00对应的分钟数,之后做差即可得到本次通话时长
};

// rate[0]表示从0-1小时,每分钟几分钱,rate[1]表示从1-2小时每分钟几毛钱,rate[23]表示从23-24小时,每分钟几毛钱
// rate[24]表示,如果一整天都在通话,每分钟几毛钱,相当于还是一个小时,只不过这一小时每分种费用是 rate[0]+rate[1]...+rate[23]
// 暂且称之为 全小时,(一整天通话压缩成一个全小时)
int rate[25] = {0};

/**
 * 假设每一个通话记录都是从0号00:00持续到现在的,得到它持续到现在的费用
 * 那么一次有效通话的是两个连续的记录,且rec1.status=1&&rec2.status=0,用rec2的费用减去rec1的费用就是本次花费
 */
double BillFromZero(Record rec) {
    // 首先经过了这么多天,也就是 天数 个 全小时,每分钟费用是 rate[24]
    double total = rec.day * 60 * rate[24];
    // 然后最后一天,持续到 rec.hour 小时,每个小时内,每分钟费用是 rate[rec.hour] 
    for (int i = 0; i < rec.hour; ++i)
        total += 60 * rate[i];
    // 最后一天,rec.hour 小时内,持续了 rec.minute分钟
    total += rec.minute * rate[rec.hour];
    // 计算出的结果是 分,要转成 元
    return total / 100.0;
}

/**
 * 不同用户的记录,按姓名排序,同一用户记录,按先是开始,再是结束的顺序排序
 */
bool cmp(Record a, Record b) {
    return a.name != b.name ? a.name < b.name : a.time < b.time;
}


int main() {

    // 输入每小时内每分钟的价格,得到 全小时 每分钟的价格
    for (int i = 0; i < 24; ++i) {
        cin >> rate[i];
        rate[24] += rate[i];
    }

    int n;
    cin >> n;
    vector<Record> record(n);
    // 得到n个记录
    for (int i = 0; i < n; ++i) {
        cin >> record[i].name;
        scanf("%d:%d:%d:%d", &record[i].month, &record[i].day, &record[i].hour, &record[i].minute);
        string temp;
        cin >> temp;
        // 得到此次记录是电话开始还是结束
        record[i].status = temp == "on-line" ? 1 : 0;
        // 转换成以0号00:00开始的分钟数
        record[i].time = record[i].day * 24 * 60 + record[i].hour * 60 + record[i].minute;
    }
    // 不同用户的记录,按姓名排序,统一用户记录,按先是开始,再是结束的顺序排序
    sort(record.begin(), record.end(), cmp);
    // 合并并筛选出有效记录,排序后,一次有效的通话记录是 同一个人连续两个记录r1和r2且r1.status=1&&r2.status=0
    map<string, vector<Record>> customer_record;
    // 对于排好序的全部记录,如果有效记录,肯定是一个on一个off
    for (int i = 1; i < n; ++i) {
        // 同一个人,一个on紧跟着一个off
        if (record[i].name == record[i - 1].name && record[i].status == 0 && record[i - 1].status == 1){
            // 保存这个人这一次有效的通话记录
            customer_record[record[i].name].push_back(record[i - 1]);
            customer_record[record[i].name].push_back(record[i]);
        }
    }
    // 遍历map并输出
    for (auto it: customer_record) {
        string name = it.first; // 键
        vector<Record> my_record = it.second; // 值
        // 第一行输出 姓名 哪一月
        cout << name;
        printf(" %02d\n", my_record[0].month);
        // 每一行是它的一次有效的通话记录及全部账单
        double bill = 0.0;
        for (int i = 1; i < my_record.size(); i += 2) {
            // 本次通话的账单
            double temp_bill = BillFromZero(my_record[i]) - BillFromZero(my_record[i - 1]);
            // 打电话天:时:分 挂电话:时:分 时长 花费
            printf("%02d:%02d:%02d %02d:%02d:%02d %d $%.2f\n", my_record[i - 1].day,my_record[i - 1].hour, my_record[i - 1].minute,
                                                              my_record[i].day, my_record[i].hour, my_record[i].minute,
                                                              my_record[i].time - my_record[i - 1].time,
                                                              temp_bill);
            bill += temp_bill;
        }
        // 打印总账单
        printf("Total amount: $%.2f\n", bill);
    }

    return 0;
}