前排提示,本题坑点:如果一个人没有合法的通话记录,那么最后结果中完全不输出
题目描述
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.
根据用户通话记录生成账单,又是一类烦人的模拟题,并不难,但是理清逻辑很重要。用Java的话,优化好应该可以过,优化的不好看脸 。
输入描述
注意到通话记录的用户名没有编号,意味着你不能用数组去存顾客的信息,而应该用map。那是用map(Java中TreeMap)好呢,还是用unorder_map(Java中HashMap)好呢?注意到这道题里面要求最后输出时,按顾客的名字字母顺序排序(字母顺序排序,见下方输出描述),果断用map,因为map底层是红黑树实现,按key排序,而我们的key又是要求排序的字符串即顾客的ID,这样最后省去排序的步骤。
输出描述
同样,如果计算时能按顺序存储记录,也能避免最后输出时对记录再次排序。
题目分析
这道题目的大致流程如下:
1、分离出每个人的通话记录
2、对于每个人的通话记录,根据on-line和off-line有效配对(就近原则),找出有效的通话记录对。
3、对于每个人的多个通话记录对,计算其所花费的电话费。由于电话费每小时都不一样,所以要分开计算。
两个核心-复杂过程:
1、如何分离出有效的通话记录对?
先将所有的单个记录排序(无论是on或者off),然后从小到大逐个取出(用一个优先队列也很合适)。每次取出一个记录T1,如果T1是offline就直接舍弃;如果T1是online,就再取出来一个记录T2,显然发生时间上T1<T2,如果T2是offline,两者恰好配对,就把这对记录留下来。如果T2也是online,那就把T2代替T1的位置。
Time* t1 = each.second[i++];
if (!t1->line) continue;
if (i == each.second.size()) {
delete t1;
break;
}
Time* t2 = each.second[i++];
if (!t2->line) per->recv.push_back(new Record(t1, t2));
while (i < each.second.size() && t2->line) {
Time* t3 = each.second[i++];
if (!t3->line){
per->recv.push_back(new Record(t2, t3));
break;
}
Time* tmp = t2;
t2 = t3;
delete tmp;
}
(为了展示程序员的良好修养,不忘了把不再用的指针删掉 )
2、如何计算一对记录中的两个时间点之间的费用之差?
由于通话时间可能跨天(真够长的 )、跨小时,我是这么实现的:
for (int i = start->day; i <= end->day; i++) {
if (i == start->day && i == end->day) {
for (int j = start->hour; j <= end->hour; j++) {
if (j == start->hour && j == end->hour)
costs += ((double)end->min - start->min) * 0.01 * pricev[j];
else if (j == start->hour && j != end->hour)
costs += ((double)60 - start->min) * 0.01 * pricev[j];
else if (j != start->hour && j != end->hour)
costs += 60 * 0.01 * pricev[j];
else
costs += end->min * 0.01 * pricev[j];
}
}
else if (i == start->day && i != end->day) {
costs += ((double)60 - start->min) * 0.01 * pricev[start->hour];
for (int j = start->hour + 1; j < 24; j++)
costs += 60 * 0.01 * pricev[j];
}
else if (i != start->day && i != end->day) {
costs += allDayCosts;
}
else {
for (int j = 0; j <= end->hour; j++) {
if (j == end->hour)
costs += end->min * 0.01 * pricev[j];
else
costs += 60 * 0.01 * pricev[j];
}
}
}
这种做法把每种情况都考虑到了,类似模拟的方法,但是很麻烦,代码行数很多。
实际上,可以这样计算: 起始时间和结束时间都假定从月初0点开始打起,再将两个费用相减。(参考@柳婼的思路)
改进后的实现如下:(代码缩减很多)
double costsFromZero(Time* t) {
double ans = t->day * allDayCosts + (double)t->min * pricev[t->hour];
for (int i = 0; i < t->hour; i++)
ans += (double)60 * pricev[i];
return (ans * 0.01);
}
costs = costsFromZero(end) - costsFromZero(start);
完整的程序也贴在下面,我用了规范的面向对象思想 冗杂的实现。定义了三个结构体分别代表时间、记录、顾客。实际上如果考试时不需要这么做,尽快算出所需要的信息,输出即可。这么写仅仅是思路比较明确。
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <sstream>
#include <algorithm>
using namespace std;
vector<int> pricev(24, 0);
double allDayCosts;
struct Time {
int day, hour, min;
bool line = false;
string toStr() {
stringstream ss;
ss << day / 10 << day % 10 << ":" << hour / 10 << hour % 10 << ":" << min / 10 << min % 10;
return ss.str();
}
};
struct Record {
Time* start, * end;
int allMin;
double costs;
double costsFromZero(Time* t) {
double ans = t->day * allDayCosts + (double)t->min * pricev[t->hour];
for (int i = 0; i < t->hour; i++)
ans += (double)60 * pricev[i];
return (ans * 0.01);
}
Record(Time* _start, Time* _end) :start(_start), end(_end), allMin(0), costs(0.0) {
allMin = (end->day * 24 * 60 + end->hour * 60 + end->min) - (start->day * 24 * 60 + start->hour * 60 + start->min);
costs = costsFromZero(end) - costsFromZero(start);
}
string toStr() {
stringstream ss;
ss << start->toStr() << " " << end->toStr() << " " << allMin << " $";
return ss.str();
}
};
struct Person {
string id;
vector<Record*> recv;
int month;
double totalAmounts;
Person(string _id, int _mon) :id(_id), month(_mon) {}
void calculate() {
totalAmounts = 0.0;
for (auto rec : recv)
totalAmounts += rec->costs;
}
};
bool cmpTime(const Time* a, const Time* b) {
if (a->day != b->day) return a->day < b->day;
else if (a->hour != b->hour) return a->hour < b->hour;
else return a->min < b->min;
}
int main() {
for (int i = 0; i < 24; i++) {
scanf("%d", &pricev[i]);
allDayCosts += pricev[i] * (double)60;
}
int N, month;
scanf("%d", &N);
map<string, Person*> pmap;
map<string, vector<Time*> > timevm;
for (int i = 0; i < N; i++) {
Time* t = new Time();
char id[25], line[10];
scanf("%s %d:%d:%d:%d %s", id, &month, &t->day, &t->hour, &t->min, line);
if (pmap.find(id) == pmap.end()) pmap.insert(make_pair(id, new Person(id, month)));
t->line = line[1] == 'n';
timevm[id].push_back(t);
}
for (auto each : timevm) {
string id = each.first;
Person* per = pmap[id];
sort(each.second.begin(), each.second.end(), cmpTime);
int i = 0;
while (i < each.second.size()) {
Time* t1 = each.second[i++];
if (!t1->line) continue;
if (i == each.second.size()) {
delete t1;
break;
}
Time* t2 = each.second[i++];
if (!t2->line) per->recv.push_back(new Record(t1, t2));
while (i < each.second.size() && t2->line) {
Time* t3 = each.second[i++];
if (!t3->line) {
per->recv.push_back(new Record(t2, t3));
break;
}
Time* tmp = t2;
t2 = t3;
delete tmp;
}
}
per->calculate();
}
for (auto it = pmap.begin(); it != pmap.end(); it++) {
Person* per = it->second;
if (per->recv.size() > 0) {
printf("%s %02d\n", per->id.c_str(), per->month);
for (int i = 0; i < per->recv.size(); i++) {
Record* rec = per->recv[i];
printf("%s%.2f\n", rec->toStr().c_str(), rec->costs);
}
printf("Total amount: $%.2f\n", per->totalAmounts);
}
}
return 0;
}
显然超出了我之前立的flag(PAT甲级代码不能超过50行),但是精简起来也很容易,这里就不做精简了。
逃~
后面还有一道模拟题。