/* 法一: 代码借鉴修改自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 "); printf("----------------------------------------------------------------------- "); 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 "); continue; } if (players[i].is_pro) { printf("%-10d",players[i].all4); printf("$%9.2lf",players[i].money); } else printf("%d",players[i].all4); printf(" "); } if (T) printf(" "); } 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 "); printf("----------------------------------------------------------------------- "); 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 "); 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 ", amount / 100.0); } else printf("%d ", 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] = ' '; 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(" "); } 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 "); printf("----------------------------------------------------------------------- "); 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 "); 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 ", amount / 100.0); } else printf("%d ", 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(" "); } return 0; }