PAT-ADVANCED1016——Phone Bills

本文介绍了解决PAT-ADVANCED中的1016手机账单问题的两种C++实现方法。第一种方法使用结构体和map对通话记录进行分类和排序,计算跨天的通话费用。第二种方法先按用户名排序,再按时间排序,通过一次遍历确定有效通话记录并计算费用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我的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++解题报告:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值