这题吧,我的评价是挺有意思。第一时间看到这题我还觉得非C++不可,后来仔细想了想发现C语言又不是不能做,真写起来发现也不是太难,写完后发现比运行C++都快(快一倍多呢)。如果你想提升一下自己,那我建议你再对这道题进行一段时间的思考。
我在这里先解释下我设置的变量,以便后续的讲解。(当然可以倒回来看)
int pet[1000][1001];//一个人都去过哪些城市,其中pet[][1000]存放这个人去了多少座城市(可有重复)
double pot[1000];//每个城市的得分
这两个是全局变量,原因有二:
1、pet太大,放函数里可能崩溃;
2、这两个都需要全初始化为0,放外面可以少两句memset(doge)。
int cpe = 0, cpo = 0;
char pe[1000][11], po[1000][11], t1[11], t2[11];
int n, t, cnt;
int uni[1000], order[1000];
注:我将people缩为pe,point缩为po。
cpe和cpo的意思分别是总人数和总城市数。
pe和po用于存放人名和城市名(不重复)。
n是输入数据数。
t是一块砖,哪里需要哪里搬。
cnt用于计算一个人去过多少地方(不重复)。
uni用于判断一个人去的地方是否已经去过。
order用于排序后存储输出顺序。
放弃思考的话就听听我的思路吧。我将这题分为5部分:输入,转义,积分,排序,输出。
这题最让人头疼的莫过于它的城市和人员是字符串,想一想,如果城市和人员的命名是从0到n-1的数字,这题是不是就相对好说了?因而,我们的第一步就是将字符串改命名为0到n-1的数字,我建议你在理解这一步后,进行自己的思考去完成这一题,毕竟后面的步骤不算过分。
int mapstringlike(char a[][11], char b[], int *c)
{
for (int i = 0; i < *c; i++)
if(!strcmp(a[i],b))
return i;
strcpy(a[(*c)++], b);
return *c - 1;
}
顺便给出使用此函数时的代码。
while(n--)
{
scanf("%s%s", t1, t2);
t = mapstringlike(pe, t1, &cpe);
pet[t][pet[t][1000]++] = mapstringlike(po, t2, &cpo);
}
解释一下函数的三个参数:
a:不重复的人名/城市名。
b:刚输入的人名/城市名。
C:已知的不重复的人名/城市名数。(用指针的原因是该值需要在函数中修改)
那么这个函数的目的很明显:将字符串组中的字符串一一对比刚输入进的字符串,如果刚输入进的字符串与任一字符串组中的字符串相同,就返回一个数字:标志着这个字符串用这个数字代替。
如果该字符串不与任何已知的字符串相同,就让不重复的人名/城市名数+1,并且存储这个字符串,然后返回一个数字:标志着这个字符串用这个数字代替。
这个函数的返回值的具体意思是:与b字符串同名的字符串在a中第一次出现时前面已出现多少不重名的字符串。当然你不用记它的意思,你只用知道这个函数的目的是将同名的字符串转化为同名的数字,不同的字符串转化为不同的数字。
由此我们已经实现将字符串转化为数字。接下来就相对简单了,在开始之前,我仍建议你尝试独立完成。
下一步是为每个城市记分。代码如下
for (int i = 0; i < cpe; i++)
{
memset(uni, 0, sizeof(uni));
cnt = 0;
for (int j = 0; j < pet[i][1000]; j++)
{
if(!uni[pet[i][j]])
uni[pet[i][j]] = ++cnt;//反正只要不是0就是真
else
pet[i][j] = -1;
}
for (int j = 0; j < pet[i][1000]; j++)
if (pet[i][j] != -1)
pot[pet[i][j]] += 1.0 / cnt;
}
uni用于存放这个城市是否已经去过。如果城市已经去过,则在后续的记分中应忽略二次出现的此城市。cnt用于存放有效城市数,以便于后续记分。
记分之后,就是排序了,代码如下
for (int i = 0; i < cpo; i++)
order[i] = i;
for (int i = 0; i < cpo; i++)
{
t = i;
for (int j = i + 1; j < cpo; j++)
if (pot[order[t]] * 1e4 < pot[order[j]] * 1e4 + strcmp(po[order[t]], po[order[j]]))
//strcmp可能在某些编译器上显示ASCII差,总之严谨的话不要学我,我这只是在减少代码长度(doge)
t = j;
if(t!=i)
{
order[i] += order[t];
order[t] = order[i] - order[t];
order[i] -= order[t];
}
}
采用选择排序。交换order而不是交换pot和po的原因也是为了减少代码长度(doge)。
*1e4的原因是为了实现先比较得分再比较字典序,毕竟strcmp才+1或-1了,要想有影响得分差要小于千分之一,很少会出这么离谱的数据吧。当然你要保险可以整个1e6什么的。
最后就剩输出了,输出完程序结束。
for (int i = 0; i < cpo; i++)
printf("%s\n", po[order[i]]);
看到这里,我想大概都懂了,我在最后给出整个程序,以便大家更好地理解。
#include<stdio.h>
#include<string.h>
int pet[1000][1001];
double pot[1000];
int mapstringlike(char a[][11], char b[], int *c)
{
for (int i = 0; i < *c; i++)
if(!strcmp(a[i],b))
return i;
strcpy(a[(*c)++], b);
return *c - 1;
}
int main()
{
int cpe = 0, cpo = 0;
char pe[1000][11], po[1000][11], t1[11], t2[11];
int n, t, cnt;
int uni[1000], order[1000];
scanf("%d", &n);
while(n--)
{
scanf("%s%s", t1, t2);
t = mapstringlike(pe, t1, &cpe);
pet[t][pet[t][1000]++] = mapstringlike(po, t2, &cpo);
}
for (int i = 0; i < cpe; i++)
{
memset(uni, 0, sizeof(uni));
cnt = 0;
for (int j = 0; j < pet[i][1000]; j++)
{
if(!uni[pet[i][j]])
uni[pet[i][j]] = ++cnt;
else
pet[i][j] = -1;
}
for (int j = 0; j < pet[i][1000]; j++)
if (pet[i][j] != -1)
pot[pet[i][j]] += 1.0 / cnt;
}
for (int i = 0; i < cpo; i++)
order[i] = i;
for (int i = 0; i < cpo; i++)
{
t = i;
for (int j = i + 1; j < cpo; j++)
if (pot[order[t]] * 1e4 < pot[order[j]] * 1e4 + strcmp(po[order[t]], po[order[j]]))
t = j;
if(t!=i)
{
order[i] += order[t];
order[t] = order[i] - order[t];
order[i] -= order[t];
}
}
for (int i = 0; i < cpo; i++)
printf("%s\n", po[order[i]]);
return 0;
}//我不怕你直接交这个的,我自己交的是直接用map<string,double>的(doge)
祝大家有一个好的暑假,在暑假期间也要认真学习哦(doge)。
看都看完了,不关个注再走吗(doge)。