UVA

/*
  法一:

  代码借鉴修改自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;
}





原文地址:https://www.cnblogs.com/mofushaohua/p/7789442.html