题目链接:https://vjudge.net/problem/UVA-207
题目描述:(简单描述)
一场高尔夫比赛,分四轮,每个选手都打完前两轮,前70名(包括并列)晋级,再打两轮,最后四轮总分前70名(包括并列)都有奖金。成绩是越少越好。
要求:给出总奖金和每个名次获奖比例(前70名),还有所有选手的名字和每轮成绩
输出所有晋级到后两轮的选手的信息,从第一名开始,并列选手按名字字典做升序输出,输出选手的名字,排名,各轮得分,总分,奖金数。
注意:
1、选手分为职业和业余,业余名字结尾带*,可晋级,参与排名,但最后没有奖金。
若业余选手得到了第三名,则第四名(非业余)拿第三名的奖金比例,以此类推
2、选手每轮都有可能犯规,即该轮成绩处记为DQ,则后面轮次没有成绩。
若为晋级选手,输出时排在最后,没有名次,总分记为DQ。
若有犯规选手并列,则先按轮数排序,然后按各轮得分之和排序,最后按名字排序
3、最后如果第k名有n名选手并列,则k~k+n-1名的奖金比例相加后平均分给这n人。
输出时并列选手名次后加一个标记T。
4、奖金四舍五入到美分。如果没取消资格(没犯规)的非业余选手小于70名,剩下的奖金就不发了。
只要选手在前70名,奖金为0也要输出。
5、输入时,只要选手没犯规,就会给出四轮成绩
(就算没晋级也会有,但在实际比赛中没晋级的选手应该只有两个成绩)
整体分析:
第一步是选出晋级选手,先对前两轮得分进行排序。接下来计算4轮总分,然后再排序一次,最后对排序结果依次输出。
题目输入格式:(原题是英文,这里是用谷歌翻译,有些地方会有出入,看得懂英文的建议点上面链接)
题目输出格式:(同上)
大致流程图:
代码:(这是GitHub上的紫书代码,加我的一些注释)
(用vs2019编译器,原码中的gets(s)不让编译,所以我都换成了cin.getline(s,40),不影响结果)
原码链接:https://github.com/aoapc-book/aoapc-bac2nd/blob/master/ch5/UVa207.cpp
//UVa207 PGA Tour Prize Money
//Rujia Liu
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cassert>
using namespace std;
#define REP(i,n) for(int i = 0; i < (n); i++)
const int maxn = 144;
const int n_cut = 70;
struct Player {
char name[25];
int amateur; //标记是否为业余选手
int sc[4]; //记录每个成绩
int sc36, //记录前两轮总分
sc72, //记录总分
dq; //标记是否犯规
int rnds; //若犯规,记录已完成的场次
} player[maxn];
int n;
double purse, p[n_cut];
bool cmp1(const Player& p1, const Player& p2) {
if (p1.sc36 < 0 && p2.sc36 < 0) return false; // equal sc36<0说明为犯规选手,无法晋级,排最后
if (p1.sc36 < 0) return false; // p2 smaller
if (p2.sc36 < 0) return true; // p1 smaller
return p1.sc36 < p2.sc36;
}
bool cmp2(const Player& p1, const Player& p2) {
if (p1.dq && p2.dq) { /*如果都是DQ选手,场次多的->总分小的->名字字典小的*/
if (p1.rnds != p2.rnds) return p2.rnds < p1.rnds;
if (p1.sc72 != p2.sc72) return p1.sc72 < p2.sc72;
return strcmp(p1.name, p2.name) < 0;
}
if (p1.dq) return false; //是否QD选手->总分小的->名字字典小的
if (p2.dq) return true;
if (p1.sc72 != p2.sc72) return p1.sc72 < p2.sc72;
return strcmp(p1.name, p2.name) < 0;
}
void print_result() {
printf("Player Name Place RD1 RD2");
printf(" RD3 RD4 TOTAL Money Won\n");
printf("---------------------------------------");
printf("--------------------------------\n");
int i = 0, pos = 0;
while (i < n) {
if (player[i].dq) //如果为DQ选手,只输出已完成的成绩,未完成的输出空格,总分记为DQ
{
printf("%s ", player[i].name);
REP(j, player[i].rnds) printf("%-5d", player[i].sc[j]);
REP(j, 4 - player[i].rnds) printf(" ");
printf("DQ\n");
i++;
continue;
}
int j = i;
int m = 0; // number of tied players 并列人数(可以分奖金的,业余不算)
bool have_money = false;
double tot = 0.0; // total pooled money
while (j < n && player[i].sc72 == player[j].sc72) { /*1、检测选手本身是否为业余选手
2、检测是否有并列,并列选手是否为业余选手*/
if (!player[j].amateur) {
m++;
if (pos < n_cut) {
have_money = true; // yeah! they still have money
tot += p[pos++];
}
}
j++;
}
// print player [i,j) together because they have the same rank
int rank = i + 1; // rank of all these m players 名次(因为i从0开始,要加1)
double amount = purse * tot / m; // if m=0, amount will be nan but we don't use it in that case :)
while (i < j) {
printf("%s ", player[i].name);
char t[5];
sprintf(t, "%d%c", rank, m > 1 && have_money && !player[i].amateur ? 'T' : ' '); //输出名次,同时检测是否有并列,有就加T
printf("%-10s", t);
REP(e, 4) printf("%-5d", player[i].sc[e]);
// with prize
if (!player[i].amateur && have_money) { //检测是否可以有奖金。(不是业余)
printf("%-10d", player[i].sc72);
printf("$%9.2lf\n", amount / 100.0);
}
else
printf("%d\n", player[i].sc72);
i++;
}
}
}
int main() {
int T;
char s[40];
cin.getline(s,40);
sscanf(s, "%d", &T);
while (T--) {
cin.getline(s, 40); // empty line
// prize
cin.getline(s, 40);
sscanf(s, "%lf", &purse);
REP(i, n_cut) {
cin.getline(s, 40);
sscanf(s, "%lf", &p[i]); //记录总奖金和每个名次所得比例
}
// players
cin.getline(s, 40);
sscanf(s, "%d", &n);
assert(n <= 144);
REP(k, n) {
// read a 32-character line
cin.getline(s, 40);
// player name
strncpy(player[k].name, s, 20); //选手名字,小于20个字符
player[k].name[20] = 0;
player[k].amateur = 0;
if (strchr(player[k].name, '*')) { //如果有‘*’,标记为业余(amateur=1)
player[k].amateur = 1;
}
// scores
player[k].sc36 = player[k].sc72 = player[k].dq = 0;
memset(player[k].sc, -1, sizeof(player[k].sc));
REP(i, 4) {
// raw score
char t[5];
REP(j, 3) t[j] = s[20 + i * 3 + j]; t[3] = '\0'; /*前面20个字符为名字,后面四个成绩,随时会有DQ,
用t[5]依次存储每个成绩,t[0]为' ',t[1],t[2]为成绩,t[3]为'\0'代表结束*/
// parse
if (!sscanf(t, "%d", &player[k].sc[i])) { /*当t[]存储不为DQ,会返回1,结果为false,运行else,记录成绩
当t[]存储为DQ,会返回0,结果为ture,运行if
标记为DQ选手(dq=-1),记录犯规的场次,如果前两场没犯规,也记录成绩,
最后要break,因为犯规后不能再参加比赛*/
// DQ!
player[k].rnds = i;
player[k].dq = -1;
if (i < 2) player[k].sc36 = -1; //前两轮有犯规,说明已经晋级不了,标记为-1
break; // skip other rounds (filled with -1, initially) //其他场次成绩已经用-1填充,前面的memset()
}
else {
player[k].sc72 += player[k].sc[i];
if (i < 2)
player[k].sc36 += player[k].sc[i];
}
}
}
// round 1
sort(player, player + n, cmp1);
assert(player[n_cut - 1].sc36 >= 0);
/*检测是否有更多选手晋级
用第70名选手前两轮分数和后一位比较
如果相同,晋级选手加一,直到有不同分数的选手(已排序,不同则说明分数更差,之后就不能晋级了)*/
for (int i = n_cut - 1; i < n; i++)
if (i == n - 1 || player[i].sc36 != player[i + 1].sc36) { n = i + 1; break; }
// round 2
sort(player, player + n, cmp2);
// print result
print_result();
if (T) printf("\n");
}
return 0;
}
void main4()
{
int i, j;
int a[10][10] = { 0 };
for (i = 1; i < 10; i++)
{
a[0][0] = 1;
a[i][0] = 1;
a[i][i] = 1;
for (j = 1; j < i; j++)
a[i][j] = a[i - 1][j - 1] + a[i - 1][j];
}
for (i = 0; i < 10; i++)
{
for (j = 0; j <= i; j++)
printf("%d ", a[i][j]);
printf("\n");
}
}
有个博客里有输入输出案例(题目没有完整给),可以自己测试https://blog.csdn.net/crazysillynerd/article/details/43763003
学到到的一些函数:
1、sscanf(s,"%d",&T):sscanf将s中的字符串以整数的形式赋给T,这里是赋予比赛数量
代码中多次用到cin.getline(s, 40);sscanf(s, "%lf", &p[i]);
可能会奇怪,为什么不直接用cin>>p[i];
这是因为有时候会从缓冲区里读到空格,导致整个程序出错
有些地方可以换,没影响,但可能是为了整体风格一致,所以统一用了sscanf。
2、sprintf(t, "%d%c", rank, m > 1 && have_money && !player[i].amateur ? 'T' : ' '):
sprintf的作用是将格式化的字符串输出到一个目的字符串中,这里是先以整数赋予名次rank,再判断该名次是否有并列选手,如果有就赋予字符T。之后输出t,就代表真正的名次了。
3、assert(n <= 144):assert是一条检查错误的语句,比如题目说不会超过144名选手。那么,当超过时,即n>144,这条语句就会终止程序,并给出错误信息(错在哪一行)
如输入n=150,执行到这里会输出Assertion failed: n <= 144,file (文件路径),line (错误行数)
不过题目一般说明不会超过144,那么系统测试数据就不会主动超过(除非人为输入),就是说这条语句不写应该也可以ac的,所以我不是很明白写这条语句的意义。
4、宏定义#define REP(i,n) for(int i = 0; i < (n); i++):这算是一个技巧,当代码中多次要用到for循环时,一个REP(i,n)再改一下参数就可以搞定。不过这是我第一次遇到,理解代码时反而会有些不适应。不过,用到多次而相似的代码时,确实可以用这个技巧。