PAT 甲级 1016 Phone Bills

前排提示,本题坑点:如果一个人没有合法的通话记录,那么最后结果中完全不输出

题目描述

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行),但是精简起来也很容易,这里就不做精简了。
逃~
后面还有一道模拟题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值