电话账单
1. 题目地址
https://www.acwing.com/problem/content/1495/
2. 题目解析
这道题的题意非常复杂,具体可以归结为:
1. 需要接收各时间段的话费信息,这里可以使用数组接收
2. 需要一个结构体来存储记录的信息:格式化时间(便于日后的输出),由时间所转换过来的分钟数(这里主要方便记录之间的排序和计算花费),该记录的状态。(用于匹配,判断该记录是否合法)
3. 由于需要按照字典序输出该用户的账单信息。因此使用map来存储:key:用户名 value:该用户的通话记录.map是天然有序的,内部为红黑树。因此:key(用户名)自动按照字典序排列。
4. 需要使用前缀和算法来预处理:某月1日0点0分开始到某分钟所需要的总花费。当需要求从S分钟开始到E分钟结束这一时间段的花费时,我们就可以使用公式:sum[E] - sum[S]; 代表:某月1日0点0分开始到E分钟所需要的总花费 - 某月1日0点0分开始到S分钟所需要的总花费。这样就求得了S分钟开始到E分钟所需要的总花费。
5. 美分到美元需要进行转换:1美元 = 100美分。
6. 总花费应为浮点数,并且需要保留两位小数。
7. 需要熟练运用:sprintf,c_str,运算符重载,格式化输入等知识点。
3. 题解
这里直接按照上述的解析做就行。唯一需要注意的就是:
1. 细节。
2. 前缀和算法。如果忘记,请参考之前博客。
4. 代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
using namespace std;
//代表最高的记录数
const int N = 1010;
//代表一个月内最多的分钟数
const int M = 31*1440 + 10;
//代表通话记录的结构体
struct Record{
int minutes; //代表该记录的分钟数(转换而来)
string status; //代表该记录的状态(在线/非在线)
string format_time; //代表标准时间格式
//由于按照分钟从小到大排序,因此这里需要重载小于号
bool operator<(const Record& c){
return minutes < c.minutes;
}
};
//将用户和该用户的通话记录关联起来
//使用map来存储:key:用户名 value:该用户的通话记录
map<string,vector<struct Record>> persons;
//代表从某月1日0点0分开始到某分钟所需要的花费(需要进行预处理)
//采用前缀和思想
double sum[M];
//存储各个时间段的话费
int cost[24];
char name[20],status[20];
int month,day,hour,minute;
int main(){
int n;
//读入话费
for(int i = 0;i < 24;i ++){
scanf("%d",&cost[i]);
}
//预处理:从某月1日0点0分开始到某分钟所需要的花费
//1. 这里注意:i - 1的原因就是:假设,i = 61, 那么第61分钟的花费实际上为:60 % 1440 / 60 = 1。
// 因此,应该算作:1:00 - 2:00 的区间当中。
// 再举一个例子:假设,i = 60, 那么第60分钟的花费实际上为:59 % 1440 / 60 = 0。
// 因此,应该算作:0:00 - 1:00 的区间当中。
// 如果这里为i,那么第60分钟的花费就算做1:00 - 2:00的区间当中了,这显然不正确。
//2. 这里将花费/100的原因就是:将美分->美元
for(int i = 1;i < M; i ++){
sum[i] = sum[i-1] + cost[(i - 1) % 1440 / 60] / 100.0;
}
//读入记录
scanf("%d",&n);
for(int i = 0;i < n;i ++){
//当读入信息时,采用scanf格式化读入
scanf("%s %d:%d:%d:%d %s",name,&month,&day,&hour,&minute,status);
//构造记录:
//1. 得到格式化后的时间,采用sprintf函数来生成格式化后的字符串
//不够的话,可用0补齐
char format_time[20];
sprintf(format_time,"%02d:%02d:%02d",day,hour,minute);
//2. 得到该记录距离当月1日0点0分的分钟数
int minutes = (day - 1) * 1440 + hour * 60 + minute;
//3. 状态不用处理,直接放到vector数组即可
persons[name].push_back({minutes,status,format_time});
}
//注意:map是天然有序的,内部为红黑树。因此:key(用户名)自动按照字典序排列。
//这里不需要处理。
//遍历map,输出信息
for(auto person : persons){
string name = person.first;
auto records = person.second;
//代表该用户所花费的总美元数
double total = 0;
//将记录按照时间(分钟数)从小到大排序
sort(records.begin(),records.end());
//这里由于要看记录是否配对,因此需要两个两个的比对
for(int i = 0; i + 1 < records.size(); i ++){
//代表记录合法
if(records[i].status == "on-line" && records[i+1].status == "off-line"){
//输出信息
// 1. 输出第一条信息:名字+月份
if(!total){
//当string使用%s输出时,需要加上方法c_str()
printf("%s %02d\n",name.c_str(),month);
}
//算出记录之间所经过的分钟数
int minutes = records[i+1].minutes - records[i].minutes;
//算出记录之间的花费
double money = sum[records[i+1].minutes] - sum[records[i].minutes];
//添加到总花费当中
total += money;
// 2. 输出第二条信息:合法记录的信息
printf("%s %s %d $%.2lf\n",records[i].format_time.c_str(),records[i+1].format_time.c_str(),minutes,money);
}
}
//如果存在总花费:输出第三条信息
if(total){
printf("Total amount: $%.2lf\n",total);
}
}
return 0;
}