UVA

来源:互联网 发布:多因子选股 知乎 编辑:程序博客网 时间:2024/06/17 10:21
/*  法一:  代码借鉴修改自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 percentagescanf("%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/7018321if (!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 debugconst 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;}





原创粉丝点击