PAT (Advanced Level) Practice——1016 Phone Bills (25)(25 分)

一、题目

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.

二、题意理解

case给出每个时间段的通话费用(美分/分钟)和若干条的起始通话时刻或结束通话时刻。对于完整通话次数大于0的用户,输出其所有完整通话的起始时刻和结束时刻以及这次通话的费用,并计算该月通话总费用。

三、难点

1、输入伊始只给出通话信息的条数,而未给出用户数量,故需用链表来动态添加新的用户。

2、通话信息是无序给出的,且并非都是有效信息(不一定能恰好配对完),故需剔除无效信息后匹配起始通话时刻和结束通话时刻。

3、起始通话时刻和结束通话时刻的时间跨度少则几分钟,多则数天,而且每个时间段的费用不同,因此求解通话费用的算法编写不太容易。

四、求解思想

1、创建三个结构体,①日期结构体,用于存储通话时刻;②通话信息结构体,用于存储日期结构体并有指向后一条信息的指针;③用户结构体,用于存储用户ID,以及起始通话时刻和结束通话时刻各自的头结点。

2、全局变量toll数组存储每个时间段的通话费用。

3、用户通话信息链表的构造:

读取通话信息时,若出现新的用户,则新增一个用户结构体并以字母在ASCII表中的顺序插入到用户链表中。根据通话信息中on-line或off-line的标志将通话时刻按时间先后顺序插入到对应用户的起始通话时刻链表或结束通话时刻链表。

4、通话时刻的匹配:

类似于对两个有序数组进行有序合并,我们在两个通话时刻链表中用到两个移动的指针。由于配对成功的结束通话时刻在起始通话时刻之后,我们依次拿结束通话时刻在起始通话时刻链表中找到相匹配的时刻。两个移动的指针均以上一次因配对成功而停下来的位置为新的出发点。

5、通话时间的计算:

将一段完整的通话分割为时间跨度不大于1小时的若干个通话时段,比如04:07:23-04:09:50可以分割成04:07:23-04:08:00、04:08:00-04:09:00、04:09:00-04:09:50。每次迭代时,起始时刻等于上一次迭代的结束时刻,而将结束时刻加1小时并判断是否超过整个通话的结束时刻。

五、C语言实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int M,toll[24];//通话信息条数,每一时间段的通话费用

typedef struct date{//日期结构体,存储通话时刻
    int month,day,hour,minute;
}Dates;

typedef struct call{//通话信息结构体
    Dates time;
    struct call *next;//下一条信息
}Calls;

typedef struct user{//用户结构体
    char ID[25];
    struct user *next;//下一个用户
    Calls *firston,*firstoff;//首条起始通话时刻信息和结束通话时刻信息
}Users;

Users* FindUser(Users* p,char *name)//在用户链表中找到对应ID的用户
{
    while(p&&strcmp(p->ID,name)!=0)
        p=p->next;
    return p;
}

Users* getNewUsers(char *name)//新增一个用户
{
    Users* newuser=(Users*)malloc(sizeof(Users));
    strcpy(newuser->ID,name);
    newuser->firstoff=newuser->firston=newuser->next=NULL;
    return newuser;
}

int CompareTime(Dates a,Dates b)//比较通话时刻的先后,a>b?
{
    if(a.day>b.day)
        return 1;
    else if(a.day<b.day)
        return 0;
    else{
        if(a.hour>b.hour)
            return 1;
        else if(a.hour<b.hour)
            return 0;
        else{
            if(a.minute>b.minute)
                return 1;
            else
                return 0;
        }
    }
}
void InsertCalls(Dates time,char *sign,Users* p)//在通话时刻链表中按时间先后顺序插入新的通话时刻信息
{
    Calls *prev=NULL,*cur;
    int flag;
    if(flag=strcmp(sign,"off-line"))//标志判断
        cur=p->firston;
    else
        cur=p->firstoff;
    while(cur&&CompareTime(time,cur->time)){//找到正确的插入位置
        prev=cur;
        cur=cur->next;
    }
    Calls* newline=(Calls*)malloc(sizeof(Calls));
    newline->time=time;
    if(!prev){//插入信息,需分首个位置和其他位置两种情况
        if(!flag){
            newline->next=p->firstoff;
            p->firstoff=newline;
        }else{
            newline->next=p->firston;
            p->firston=newline;
        }
    }else{
        newline->next=prev->next;
        prev->next=newline;
    }
}
int CompareID(char *a,char *b)//比较用户ID的前后,a<b?
{
    int i,lena=strlen(a),lenb=strlen(b),len;
    len=lena>lenb?lenb:lena;//循环次数取短的字符串长度
    for(i=0;i<len;++i){
        if(a[i]<b[i])
            return 1;
        else if(a[i]>b[i])
            return 0;
    }
    if(lena<lenb)//字符串b包含字符串a
        return 1;
    else
        return 0;
}
void Read(Users* ptr)
{
    int i;
    char name[25],sign[10];
    Users* p;
    Dates time;
    for(i=0;i<M;++i){
        scanf("%s %d:%d:%d:%d %s",name,&time.month,&time.day,&time.hour,&time.minute,sign);
        if(!(p=FindUser(ptr->next,name))){
            p=getNewUsers(name);
            Users *prev=ptr,*cur=ptr->next;
            while(cur&&CompareID(cur->ID,p->ID)){
                prev=cur;
                cur=cur->next;
            }
            p->next=prev->next;
            prev->next=p;
        }
        InsertCalls(time,sign,p);
    }
}
int CountTime(Dates on)//将通话时刻转化为截止到目前的时间
{
    return on.day*24*60+on.hour*60+on.minute;
}

Dates TimeAddOneHour(Dates time)//通话时刻+1小时
{
    if(++time.hour==24){
        time.day++;
        time.hour=0;
    }
    time.minute=0;
    return time;
}
int TimeSubtract(Dates left,Dates right)//计算每个通话时段的通话费用
{
    int minute;
    minute=left.hour==right.hour?right.minute-left.minute:60-left.minute;
    return minute*toll[left.hour];
}
float CallFare(Dates on,Dates off)//将完整的通话分割为若干个通话时段
{
    float amount=0;
    int flag=1;
    Dates left,right;
    right=on;
    do{
        left=right;
        right=TimeAddOneHour(right);
        if(CompareTime(right,off)){
            right=off;
            flag=0;
        }
        amount+=TimeSubtract(left,right);
    }while(flag);
    return amount/100.0;
}
void MatchTime(Users *p)//匹配起始通话时刻和结束通话时刻
{
    int flag=1;
    Calls *off=p->firstoff,*on,*cur=p->firston,*prev;//两个移动的指针
    float total=0;
    float fare;
    while(off){//外部是结束通话时刻链表的指针
        prev=NULL;
        on=cur;
        while(on&&CompareTime(off->time,on->time)){//内部是起始通话时刻链表的指针,找到当前不超过结束时刻的最晚起始时刻
            prev=on;
            on=on->next;
        }
        if(prev){//匹配成功
            if(flag){//第一次输出,需打印用户ID和月份
                printf("%s %02d\n",p->ID,prev->time.month);
                flag=0;
            }
            PrintTime(prev->time);
            PrintTime(off->time);
            printf("%d ",CountTime(off->time)-CountTime(prev->time));//计算通话时间
            printf("$%.2f\n",fare=CallFare(prev->time,off->time));//计算通话费用
            total+=fare;//费用累加
            cur=prev->next;
        }
        off=off->next;
    }
    if(!flag)//如果该用户有过一次完整的通话则输出通话总费用
        printf("Total amount: $%.2f\n",total);
}
void PrintTime(Dates time)//打印通话时刻
{
    printf("%02d:%02d:%02d ",time.day,time.hour,time.minute);
}
int main()
{
    int i;
    for(i=0;i<24;++i)
        scanf("%d",&toll[i]);
    scanf("%d",&M);
    Users *ptr=(Users*)malloc(sizeof(Users));//用户链表的头结点
    ptr->next=NULL;
    Read(ptr);
    Users *p=ptr->next;
    while(p){//对每位用户依次进行通话时刻的匹配
        MatchTime(p);
        p=p->next;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值