例题5-10 PGA巡回赛的奖金(PGA Tour Prize Money,UVa207)题解(给出输入数据随机生成程序)

欢迎访问我的Uva题解目录哦 https://blog.csdn.net/richenyunqi/article/details/81149109

题目描述

题意解析

任务是为PGA(美国职业高尔夫球协会)巡回赛计算奖金。巡回赛分为4轮,其中所有选手都能打前两轮(除非中途取消资格),得分相加(越少越好),前70名(包括并列)晋级(make the cut)。所有晋级选手再打两轮,前70名专业选手(包括并列)有奖金。组委会事先会公布每个名次能拿的奖金比例。例如,若冠军比例是18%,总奖金是$1000000,则冠军奖金是$180000。
输入保证冠军不会并列。如果第k名有n人并列,则第k~n+k-1名的奖金比例相加后平均分给这n个人。奖金四舍五入到美分。所有业余选手不得奖金。例如,若业余选手得了第3名,则第4名会拿第3名的奖金比例。如果没取消资格的非业余选手小于70名,则剩下的奖金就不发了。
输入第一行为数据组数。每组数据前有一个空行,然后分为两部分。第一部分有71行(各有一个实数),第一行为总奖金,第i+1行为第i名的奖金比例。比例均保留4位小数,且总和为100%。第72行为选手数(最多144),然后每行一个选手,格式为:
Player name RD1 RD2 RD3 RD4
业余选手名字后会有一个“*”。犯规选手在犯规的那一轮成绩为DQ,并且后面不再有其他成绩。但是只要没犯规,即使没有晋级,也会给出4轮成绩(虽然在实际比赛中没晋级的选手只会有两个成绩)。输入保证至少有70个人晋级。
输出应包含所有晋级到后半段(make the cut)的选手。输出信息包括:选手名字、排名、各轮得分、总得分以及奖金数。没有得奖则不输出,若有奖金,即使奖金是$0.00也要输出,保留两位小数)。如果此名次至少有两个人获得奖金,应在名次后面加“T”。犯规选手列在最后,总得分为DQ,名次为空。如果有并列,则先按轮数排序,然后按各轮得分之和排序,最后按名字排序。两组数据的输出之间用一个空格隔开。

算法设计

这道模拟题目坑非常多,题目描述中没有给出完整的样例数据,可以在uDebug中获取。我在这给出一份针对本题的不是十分严谨的输入数据随机生成程序,以方便大家测试自己的程序:

#include
using namespace std;
int main(){
    srand((int)time(0));
    int N=rand()%5+100;//输入数据组数(在100~104之间) 
    printf("%d\n",N);
    while(N--){
        printf("\n1000000.00\n");//总奖金额为100万 
        for(int i=0;i<70;++i)//随机输出70个奖金分配比例,注意这些比例之和是随机的,不是正好为100% 
            printf("%.4f\n", ((double)rand()/RAND_MAX)*100);
        printf("144\n");//144位选手 
        for(int i=0;i<144;++i){
            string s=to_string(i);//以数字0~143为选手姓名 
            if(rand()%97==0){//下一个随机数正好能被97整除时在选手姓名后加*,即保证有一定概率的选手是业余选手 
                s+="*";
            }
            printf("%-20s",s.c_str());//选手姓名占20位字符,且左对齐 
            for(int j=0;j<4;++j){//输出4轮成绩 
                int t= rand()%31+69;//每轮成绩在70~99之间 
                if(t<70){//如果成绩小于70
                    printf(" DQ");//输出DQ,即保证有一定概率犯规 
                    goto loop;//跳出循环 
                }
                printf("%3d",t);//输出每轮成绩,成绩占3位,右对齐 
            }
            loop:puts("");//换行
        }
    }
    return 0;
}

有了输入数据随机生成程序,就可以用我在这篇博客中AC的代码进行程序对拍,也可以针对特殊的输入数据,用我的程序输出相应的输出数据,方便检测自己代码是否正确。
本题算法其实不难,主要是要求太多,需要一定的细心和耐心,我在AC的代码中补充了足够的注释,大家直接看我的代码吧。

C++代码

#include
using namespace std;
struct Player{//选手类
    string playername="";//姓名
    int RD[4]={-1,-1,-1,-1};//四轮成绩
    int total=-1;//四轮成绩之和
    int place=1;//排名
    int roundNum=0;//参加的比赛轮数,例如前两轮没有犯规,第三轮犯规,则roundNum=3
    double money=0.0;//获得奖金
    bool amateur=false;//是否是业余选手
    bool T=false;//是否需要在排名后输出'T'
    bool prize=false;//是否可以拿到奖金
};
int main(){
    int N;
    scanf("%d",&N);//数据组数
    double purse[71]={0.0};//奖金比例,purse[0]存储总奖金额
    int playerNumber;//选手人数
    for(int ii=0;ii<N;++ii){
        if(ii>0)//每两组测试数据之间输出一个空行
            puts("");
        scanf("%lf",&purse[0]);//读取总奖金额
        for(int i=1;i<71;++i)//读取奖金比例
            scanf("%lf",&purse[i]);
        scanf("%d%*c",&playerNumber);//读取选手人数,%*c吸收换行符
        Player players[playerNumber];
        string line;
        for(int i=0;i<playerNumber;++i){//读取每个选手的信息
            getline(cin,line);//读取整行字符
            //如果整行字符中有'*'字符,表明是业余选手
            players[i].amateur=(find(line.begin(),line.end(),'*')!=line.end());
            //分割选手姓名,注意这里直接读取了21个字符,因为输出姓名时也需要输出21个字符
            players[i].playername=line.substr(0,21);
            players[i].total=0;
            for(int j=0;j<4;++j){//分割4轮成绩
                string t=line.substr(21+j*3,2);//分割成绩
                if(t=="DQ")//犯规,直接跳出循环
                    break;
                ++players[i].roundNum;//递增参加的比赛轮数
                players[i].RD[j]=stoi(t);//读取该轮成绩
            }
            //先取前两轮成绩之和,如果前两轮有犯规,将成绩之和置为400,因为成绩越小排名越靠前,置为400可以保证犯规选手排序后位于末尾
            players[i].total=(players[i].roundNum<2)?400:players[i].RD[0]+players[i].RD[1];
        }
        sort(players,players+playerNumber,[](const Player&p1,const Player&p2){//按成绩由小到大排序
            return p1.total<p2.total;
        });
        playerNumber=70;//晋级选手人数初始化为70
        //如果有和70名选手相同成绩的,递增晋级选手人数
        while(players[playerNumber].total==players[69].total)
            ++playerNumber;
        for(int i=0;i<playerNumber;++i){//计算每位晋级选手的4轮成绩之和,犯规成绩不计入
            if(players[i].RD[2]!=-1)
                players[i].total+=players[i].RD[2];
            if(players[i].RD[3]!=-1)
                players[i].total+=players[i].RD[3];
        }
        sort(players,players+playerNumber,[](const Player&p1,const Player&p2){//排序
            if(p1.roundNum!=p2.roundNum)//先按比赛轮数由大到小排序
                return p1.roundNum>p2.roundNum;
            else if(p1.total!=p2.total)//接着按成绩之和有小到大排序
                return p1.total<p2.total;
            else//最后按姓名排序
                return p1.playername<p2.playername;
        });
        int purseIndex=1;//当前分配的奖金比例索引
        for(int i=0;i<playerNumber;){//遍历每一个晋级选手
            int j=i,profession=0;//profession为与当前选手成绩相同的专业选手人数
            for(;j<playerNumber&&players[j].total==players[i].total;++j){//遍历与当前当前选手成绩相同的选手
                if(!players[j].amateur){//是专业选手
                    ++profession;//递增profession
                    players[j].prize=(purseIndex<=70);//是否获奖取决于是否还有未分配的奖金比例
                }
            }
            double currentPurse=0.0;
            //统计与当前选手成绩相同的专业选手获取的奖金比例总和
            for(int k=0;k<profession&&purseIndex<=70;++k)
                currentPurse+=purse[purseIndex++];
            currentPurse*=purse[0]/100/profession;//计算每一位专业选手获得的奖金额
            for(int k=i;k<j;++k){//遍历与当前当前选手成绩相同的选手
                players[k].place=i+1;//更新当前选手排名
                players[k].money=players[k].prize?currentPurse:0.0;//计算获取的奖金额
                //当且仅当当前选手是专业选手且有并列的专业选手且可以获奖时,才需要在排名后输出'T'
                players[k].T=!players[k].amateur&&profession>1&&players[k].prize;
            }
            i=j;
        }
        puts("Player Name          Place     RD1  RD2  RD3  RD4  TOTAL     Money Won");
        puts("-----------------------------------------------------------------------");
        for(int i=0;i<playerNumber;++i){//遍历每一个晋级选手
            string place="";//输出的排名字符串
            if(players[i].roundNum==4)//如果参加了4轮比赛,才需要输出排名
                place=to_string(players[i].place)+((players[i].T)?"T":"");
            printf("%s%-10s",players[i].playername.c_str(),place.c_str());//输出姓名和排名
            for(int j=0;j<4;++j)//输出4轮成绩
                if(players[i].RD[j]==-1)//没有该轮成绩
                    printf("     ");//输出5个空格符
                else
                    printf("%-5d",players[i].RD[j]);//成绩占5个字符,左对齐
            if(players[i].roundNum<4)//选手有犯规
                printf("DQ");//输出"DQ"
            else if(players[i].amateur||!players[i].prize)//选手是业余选手或者没有获奖的专业选手
                printf("%d",players[i].total);//只输出成绩总和
            else//否则输出成绩总和以及获得的奖金
                printf("%-10d$%9.2f",players[i].total,players[i].money);
            puts("");//换行
        }
    }
    return 0;
}

你可能感兴趣的:(算法竞赛入门经典,-,Uva)