/*
法一:
代码借鉴修改自blog:
http://blog.csdn.net/aozil_yang/article/details/50543965
这个博客把思路和注意的地方说得很详细了,建议一看
收获:
1. 如何在字符串中招某个特定的字符?
可用strchr()函数:
http://blog.csdn.net/tommy_wxie/article/details/7554263
2. 浮点数精度的控制 +eps 以及eps的选取:
http://www.cnblogs.com/oyking/p/3959905.html
http://www.cnblogs.com/acsmile/archive/2011/05/09/2040918.html
3.fgets()函数 代替容易缓冲溢出的 gets()函数:
http://www.cnblogs.com/aexin/p/3908003.html
4.博主用了一个很巧妙的思路:将70个专业选手的奖金比例分配完之后,其他的选手都标记为业余选手,这样在控制是否输出奖金时,就会更方便。
但是在循环中,这步就不太好处理了,很容易在边界上出错,考虑时必须谨慎周密!
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 200;
const int maxm = 50;
const double eps = 1e-8;
const int DQ = 0x3f3f3f3f;
int get_len(int x)
{
int ans = 0;
while (x)
{
ans++;
x /= 10;
}
return ans;
}
struct player
{
char name[maxm];
double money;
int rk, all2, all4, sco[5];
bool is_pro, is_T;
}players[maxn];
//先按各轮得分排序,再按名字排序
bool cmp1(const player &a, const player& b)
{
if (a.all2 != b.all2) return a.all2 < b.all2;
return strcmp(a.name, b.name) < 0;
}
bool cmp2(const player &a, const player &b)
{
if (a.all4 != b.all4) return a.all4 < b.all4;
return strcmp(a.name, b.name) < 0;
}
bool cmp3(const player &a, const player &b)
{
int cnta = 0, cntb = 0;
for (int i = 0; i < 4; i++)
{
if (a.sco[i] == DQ)
{
cnta = i;
break;
}
}
for (int i = 0; i < 4; i++)
{
if (b.sco[i] == DQ)
{
cntb = i;
break;
}
}
if (cnta != cntb) return cnta > cntb;//轮数大的排前面
if (a.all4 != b.all4) return a.all4 < b.all4;//分数小的排前面
return strcmp(a.name, b.name) < 0;//名字是按字典序排
}
int main()
{
int T, n;
double money_sum, per[maxn]; // array for percentage
scanf("%d", &T);
while (T--)
{
memset(players, 0, sizeof(players));
memset(per, 0, sizeof(per));
scanf("%lf", &money_sum);
for (int i = 0; i < 70; i++) scanf("%lf", per + i);
scanf("%d", &n);
getchar();
for (int i = 0; i < n; i++)
{
fgets(players[i].name, 20, stdin);
//scanf的返回值为正确接收变量的个数,若接收DQ,因为不是scanf要求的整型数据,scanf返回值为0
//有关讲解: http://blog.csdn.net/linuxxulin/article/details/7018321
if (!strchr(players[i].name, '*')) players[i].is_pro = true;
for (int j = 0; j < 4; j++)
{
int flag = 0;
if (!scanf("%d", &players[i].sco[j]))
{
players[i].sco[j] = DQ;
flag = 1;
}
if (j < 2) players[i].all2 += players[i].sco[j];
players[i].all4 += players[i].sco[j];
if (flag) break;
}
char temp[15];
fgets(temp, 10, stdin);
//这是为了接收空白的一行,因为题目有:This line is followed by a blank line, and there is also a blank line between two consecutive inputs.
}
sort(players, players + n, cmp1);
int pos = 0, pos2 = 0;
while (pos < 70 ) pos++; //题目给定至少70人晋级
while (pos < n && players[pos].all2 == players[pos - 1].all2) pos++;//找并列70名
sort(players, players + pos, cmp2);
while (pos2 < pos && players[pos2].all4 < DQ) pos2++;//这个循环是找到犯规的人从哪开始
if (pos != pos2) sort(players + pos2, players + pos, cmp3);
//如果不相等,必定是三四轮出现了犯规的人,犯规的区间进行sort排序,并且重新写出一个排序标准
int rank = 1, cur = 0, pos3, cnt = 0;
//统计总分相等的,得到相等区间 [cur, pos3)(该相等区间中,还需要排除掉不拿奖金的业余选手),相等区间内的人平分他们的奖金之和
while (cur < pos2)
{
int sum = 0;// 区间内的人数
double ave_per = 0;//区间内的人的平均奖金百分比
for (pos3 = cur; players[pos3].all4 == players[cur].all4; pos3++)
{
if (players[pos3].is_pro)
{
sum++; ave_per += per[cnt++];//cnt表示当前发到奖金比例的下标,注意如果为不得奖的业余选手,他的比例会被后面的得奖选手用掉
}
}
if (sum) ave_per /= sum;//得到区间平均奖金比例百分数(尚未/100,不是真正所占的百分比)
//注意sum是否为0的判断千万不要忘写,否则程序很可能异常
for (int i = cur; i < pos3; i++)
{
players[i].rk = rank;//相等区间所有人等级一致
if (players[i].is_pro && sum) players[i].money = ave_per * money_sum / 100.0 + eps;//加eps避免浮点误差
if (players[i].is_pro && sum > 1 && cnt - sum < 70) players[i].is_T = true;
//如果还属于并列前70名可拿奖金的选手(业余不可拿),且该等级对应有至少两个可拿奖金的并列选手,则为它标号T,表示该名次有人并列
if (cnt - sum >= 70) players[i].is_pro = false;
//将比例数值大于70的那些运动员自动设为非职业运动员,这样输出可以直接判断是否为职业运动员来进行输出奖金,这样设置是因为,这两类运动员都是没有奖金的
}
int temp = pos3 - cur;
cur += temp;
rank += temp;//等级和区间起点都要更新
}
printf("Player Name Place RD1 RD2 RD3 RD4 TOTAL Money Won\n");
printf("-----------------------------------------------------------------------\n");
for (int i = 0; i < pos; i++)//pos为晋级到后半段的人数
{
printf("%-21s",players[i].name);
int temp = 10;//temp是用来控制格式的,表示输完名次,以及输完可能存在的T以后,还需要输出几个空格
if (players[i].all4 < DQ) //没犯规的人才有名次
{
printf("%d",players[i].rk);
temp -= get_len(players[i].rk);
}
if (players[i].is_T)//并列则输出并列符号
{
printf("T");
temp--;
}
for (int i = 0; i < temp; i++)printf(" ");
temp = 4;
for (int j = 0; j < 4; j++)
{
if (players[i].sco[j] != DQ) printf("%-5d",players[i].sco[j]);
else
{
temp -= j;
break;
}
//temp在该轮循环中,是为了找到DQ是否出现,出现在哪一局的得分栏,并且输出连续空格串控制格式,以输出TOTAL栏的DQ
}
if (temp == 4) temp = 0;
for (int i = 0; i < temp; i++) printf(" ");
if (temp)
{
printf("DQ\n");
continue;
}
if (players[i].is_pro)
{
printf("%-10d",players[i].all4);
printf("$%9.2lf",players[i].money);
}
else printf("%d",players[i].all4);
printf("\n");
}
if (T) printf("\n");
}
return 0;
}
/*
法二参考《入门经典》的代码,理解后手敲了一次
收获:
1. 宏定义用在循环中,简化代码,如:
#define REP(i,n) for(int i = 0; i < (n); i++)
需要包含头文件 #include<cassert>
2. gets()和sscanf()的配合使用,从有空格的字符串中分离出需要的数据
该处理方法也可见:
http://blog.csdn.net/lujiandong1/article/details/41439849
3. assert宏在测试时的使用(见入门经典P123 或 blog: http://www.cplusplus.com/reference/cassert/assert/)
assert宏的用法:assert(表达式);
作用:当表达式为真时无变化,当表达式为加时强制终止程序,并给出错误提示(在测试时经常使用)
4. 用sprintf函数将一些数据按照一定的格式写到字符串中(尤其适合格式控制,例如不足几位补0,左对齐右对齐等等)
http://www.cplusplus.com/reference/cstdio/sprintf/
*******两种方法比较:
刘汝佳前辈的代码比之法一,有个很大的改进之处,在于,法二的代码的结构体中,增加了两个元素
dq(表示是否犯规),rnds(有违规时,用之记录哪局违规)
加了两个数据以后,输出时会变得更方便...而对违规的记录,法一是用一个很大的数来代替违规时的分数,使之必然排到最后
*/
#include <cstdio>
#include <cstring>
#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[50];
int amateur;
int sco[4];
int all2, all4, dq;
int rnds;
} player[maxn];
int n;
double mon_all, p[n_cut];
//cmp1功能:比完前两局晋级时,有违规的排不曾违规的人后面;对没违规的选手,总分低的人排总分高的人前面
//cmp1的排序虽然还不完全,但已经足以将并列前70名提取出来,并将有违规的人排到没违规的人后面
bool cmp1(const Player &a, const Player &b)
{
if (a.all2 < 0 && b.all2 < 0) return false;
if (a.all2 < 0) return false;
if (b.all2 < 0) return true;
return a.all2 < b.all2;
}
bool cmp2 (const Player &a, const Player &b)
{
if (a.dq && b.dq) //两选手都违规
{
if (a.rnds != b.rnds) return b.rnds < a.rnds;
if (a.all4 != b.all4) return a.all4 < b.all4;
return strcmp(a.name, b.name) < 0;
}
if (a.dq) return false;
if (b.dq) return true; //一人违规
if (a.all4 != b.all4) return a.all4 < b.all4;
return strcmp(a.name, b.name) < 0;
//两人都没违规,先比较总分,再将名字按字典序排列
}
void solve()
{
printf("Player Name Place RD1 RD2 RD3 RD4 TOTAL Money Won\n");
printf("-----------------------------------------------------------------------\n");
int i = 0, pos = 0;
while (i < n)
{
if (player[i].dq) //如果有犯规,则后面的比赛都无法参加,没有总分也没有奖金
{
printf("%s ",player[i].name); //名字在输入时已经控制好了长度,所以不必在printf输出时进行格式控制
REP(j,player[i].rnds) printf("%-5d", player[i].sco[j]);
REP(j,4-player[i].rnds) printf(" ");
printf("DQ\n");
i++;
continue;
}
int j = i;
int m = 0; //等级相同的专业选手数
bool is_divide = false;
double tot = 0.0; //平均百分比
while (j < n && player[j].all4 == player[i].all4)
{
if (!player[j].amateur) //非业余选手才有奖金
{
m++;
if (pos < n_cut) //并列前70名才有奖金比例的累加,否则即使是专业选手,也没有奖金分
{
is_divide = true; // is_divide表示当前名次有并列获奖者,需要平分奖金
tot += p[pos++]; //pos记录当前循环到哪个奖金比例,业余选手不占用比例
}
/*这里有个小细节要注意:
如果由于可并列的原因,70个奖金百分比已经分完了,但是还有并列前70的人没枚举,这时候就是只累加人数,不累加奖金比例了
因为凡是在并列前70的都能平分,不过奖金比例不会变多,相当于如果是最后一组并列70的人,有可能会有5人平分3人份的奖金百分比的情况
*/
}
j++;
}
//打印下标在[i,j)范围内的选手信息,因为他们的等级相等,(若有奖金),奖金也相等,因而一并处理
int rank = i + 1;
double amount = mon_all * tot / m;
/*
注意此处,之所以不必检查m是否为0,是因为按照代码循环条件的控制,如果能进入外部循环,必定已经满足 i < n,而j又从i开始循环,因而m=0仅可能出现在一种情况:
这个编号为i的运动员自己是业余的,并且没有与之并列的专业运动员,这种情况下, amount当然会是一个无效的值,但是这种情况下,也不满足 amountd的输出条件 if(!player[i].amateur && is_divide) ,也就是这个无效值并不会被输出
因此,若将 double amount = mon_all * tot / m; 换为
double amount; if (m) amount = mon_all * tot / m;
也完全没问题,我试过仍能AC,因为amount无效的情况下,程序也确实没有试图输出它,所以看上去是不会有什么影响的,当然有没有隐患就是另一个问题了...
*/
while (i < j)
{
printf("%s ", player[i].name);
char t[5];
sprintf(t, "%d%c", rank, m > 1 && is_divide && !player[i].amateur? 'T' : ' '); //并且只有专业选手参与平分奖金,业余选手仅排名不拿奖
printf("%-10s", t);
REP(e, 4) printf("%-5d", player[i].sco[e]);
if (!player[i].amateur && is_divide)
{
printf("%-10d", player[i].all4);
printf("$%9.2lf\n", amount / 100.0);
}
else
printf("%d\n", player[i].all4);
i++;
}
}
}
int main()
{
int T;
char s[40];
gets(s);
sscanf(s, "%d", &T);
//注意这题由于每组数据前,都有整行的空白行,所以格式的处理务必小心,尤其注意,不要因为回车符没有处理好,导致本来该读整个空白行的代码,变成了读长度为0的空串,最后导致 RE
//上面两行可以用下面两行代替,千万注意别忘了 getchar(),否则RE
// scanf("%d",&T);
// getchar();
while (T--)
{
gets(s); //读取整行空行
gets(s);
sscanf(s, "%lf", &mon_all);
//奖金比例
REP(i, n_cut)
{
gets(s);
sscanf(s, "%lf", p + i);
}
//选手信息
gets(s);
sscanf(s, "%d", &n);
assert(n <= 144);
REP(k, n)
{
gets(s);
strncpy(player[k].name, s, 20);
player[k].name[20] = 0; //strcpy和strncpy的差异之一就是,strncpy用于复制时,是不会自动加上结束符的,需要自己加上
player[k].amateur = 0;
if (strchr(player[k].name, '*')) player[k].amateur = 1;
player[k].all2 = player[k].all4 = player[k].dq = 0;
memset(player[k].sco, -1, sizeof(player[k].sco));
REP(i, 4)
{
char t[5];
REP(j, 3) t[j] = s[20 + i * 3 + j]; t[3] = '\0';
if (!sscanf(t, "%d", &player[k].sco[i]))
{
player[k].rnds = i;
player[k].dq = -1;
if (i < 2) player[k].all2 = -1;
break; //若某一回合成绩为dq,作处理如下,因为初始化为-1了,所以只需要在违规那局标记分数即可
}
else
{
player[k].all4 += player[k].sco[i];
if (i < 2)
player[k].all2 += player[k].sco[i];
}
}
}
//晋级初赛
sort(player, player+n, cmp1);
assert(player[n_cut - 1].all2 >= 0);
for (int i = n_cut - 1; i < n; i++)
if (player[i].all2 != player[i + 1].all2)
{
n = i + 1;
break;
} //找和第70名并列的,n的意义从此变为初赛晋级人数
sort(player, player+n, cmp2);
//cmp2是题目真正要求实现的排列方式
solve();
if (T) printf("\n");
}
return 0;
}
/*
edition3:
下面的版本摘取了法二中值得学习的地方,但大部分格式控制并不采用法二中的"gets + sscanf",因为个人觉得这个比直接scanf更容易出错和弄混,不过也有很大可能是我对此还不够熟练...
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
using namespace std;
#define REP(i, n) for (int i = 0; i < (n); i++)
//#define debug
const int maxn = 144;
const int n_cut = 70;
struct Player
{
char name[50];
int amateur;
int sco[4];
int all2, all4, dq;
int rnds;
} player[maxn];
int n;
double mon_all, p[n_cut];
bool cmp1(const Player &a, const Player &b)
{
if (a.all2 < 0 && b.all2 < 0) return false;
if (a.all2 < 0) return false;
if (b.all2 < 0) return true;
return a.all2 < b.all2;
}
bool cmp2 (const Player &a, const Player &b)
{
if (a.dq && b.dq) //两选手都违规
{
if (a.rnds != b.rnds) return b.rnds < a.rnds;
if (a.all4 != b.all4) return a.all4 < b.all4;
return strcmp(a.name, b.name) < 0;
}
if (a.dq) return false;
if (b.dq) return true; //一人违规
if (a.all4 != b.all4) return a.all4 < b.all4;
return strcmp(a.name, b.name) < 0;
//两人都没违规,先比较总分,再将名字按字典序排列
}
void solve()
{
printf("Player Name Place RD1 RD2 RD3 RD4 TOTAL Money Won\n");
printf("-----------------------------------------------------------------------\n");
int i = 0, pos = 0;
while (i < n)
{
if (player[i].dq)
{
printf("%s ",player[i].name);
REP(j,player[i].rnds) printf("%-5d", player[i].sco[j]);
REP(j,4-player[i].rnds) printf(" ");
printf("DQ\n");
i++;
continue;
}
int j = i;
int m = 0; //等级相同的专业选手数
bool is_divide = false;
double tot = 0.0; //平均百分比
while (j < n && player[j].all4 == player[i].all4)
{
if (!player[j].amateur)
{
m++;
if (pos < n_cut)
{
is_divide = true;
tot += p[pos++];
}
}
j++;
}
int rank = i + 1;
double amount = mon_all * tot / m;
while (i < j)
{
printf("%s ", player[i].name);
char t[5];
sprintf(t, "%d%c", rank, m > 1 && is_divide && !player[i].amateur? 'T' : ' '); //并且只有专业选手参与平分奖金,业余选手仅排名不拿奖
printf("%-10s", t);
REP(e, 4) printf("%-5d", player[i].sco[e]);
if (!player[i].amateur && is_divide)
{
printf("%-10d", player[i].all4);
printf("$%9.2lf\n", amount / 100.0);
}
else
printf("%d\n", player[i].all4);
i++;
}
}
}
int main()
{
int T;
char s[40];
scanf("%d",&T);
while (T--)
{
scanf("%lf", &mon_all);
//奖金比例
REP(i, n_cut) scanf("%lf", &p[i]);
scanf("%d", &n);
assert(n <= 144);
getchar();
REP(k, n)
{
fgets(player[k].name, 21, stdin);
player[k].amateur = 0;
if (strchr(player[k].name, '*')) player[k].amateur = 1;
player[k].all2 = player[k].all4 = player[k].dq = 0;
memset(player[k].sco, -1, sizeof(player[k].sco));
REP(i, 4)
{
if (!scanf("%d", &player[k].sco[i]))
{
player[k].rnds = i;
player[k].dq = -1;
if (i < 2) player[k].all2 = -1;
break;
}
else
{
player[k].all4 += player[k].sco[i];
if (i < 2)
player[k].all2 += player[k].sco[i];
}
}
char temp[15];
fgets(temp, 10, stdin);
}
sort(player, player+n, cmp1);
assert(player[n_cut - 1].all2 >= 0);
for (int i = n_cut - 1; i < n; i++)
if (player[i].all2 != player[i + 1].all2)
{
n = i + 1;
break;
}
sort(player, player+n, cmp2);
solve();
if (T) printf("\n");
}
return 0;
}