原题链接添加链接描述
思索一番,该题还是写一篇文章,因为对于我帮助挺大的,其实也是困扰了半天。
直观思路
本题,最直观的思路是,使用unordered_map < string,double > 建立学校到成绩的映射,使用unordered_map < string,int > cnt建立从学校到人数的映射。使用vector < string > total 来存储下所有出现过的学校。
代码如下
#include <bits/stdc++.h>
using namespace std;
vector<string> total;
unordered_map<string,double> tws;
unordered_map<string,int> cnt;
bool cmp(string &a,string &b){
if((int)tws[a] != (int)tws[b]) return (int)tws[a] > (int)tws[b];
else if(cnt[a] != cnt[b]) return cnt[a] < cnt[b];
else return a < b;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++){
char id[7];
double grade;
string name;
scanf("%s%lf",id,&grade);
cin>>name;
transform(name.begin(),name.end(),name.begin(),::tolower);
if(!cnt[name]) total.push_back(name);
cnt[name]++;
if(id[0] == 'B') grade /= 1.5;
else if(id[0] == 'T') grade *= 1.5;
tws[name]+=grade;
}
sort(total.begin(),total.end(),cmp);
cout<<total.size()<<endl;
int r = 1;
for(int i=0;i<total.size();i++){
if(i && (int)tws[total[i]] != (int)tws[total[i-1]] ) r = i + 1;
cout<<r<<' '<<total[i]<<' '<<(int)tws[total[i]]<<' '<<cnt[total[i]]<<endl;
}
return 0;
}
提交试试
可以看到,测试点4和5超时。代码中能用scanf和printf的地方,都使用了scanf和printf,还是超时,只能是这个思路是不行的。但是这个思路是非常直观的,只不过后两个测试点数据量太大,不适用。
另谋他路
另外的思路是,因为题目告知n最大为100000,也就是,最多有100000所学校,这样,就使用结构体school来存放学校信息,包括,该学校人数、tws,以及学校名字。并且开辟一个大小为100000大小的结构体数组,这样,最重要的是要建立从学校到数组下标的映射,即unordered_map < string,int > name_id,当读入一个数据时,将学校中的大写字母变成小写字母之后,查询一下是否已经有了从学校到结构体数组的映射,如果已经有了映射,则直接在映射结果的结构体上修改数据,否则,建立映射,添加数据。
#include <bits/stdc++.h>
using namespace std;
struct school{
int cnt;
double tws;
string name;
school(){
cnt = 0;
tws = 0.0;
}
bool operator < (const school &b) const{
if((int)tws != (int)b.tws) return (int)tws > (int)b.tws;
else if(cnt != b.cnt) return cnt < b.cnt;
else return name < b.name;
}
};
int number = 0;
const int maxn = 1e5+1;
vector<school> total(maxn);
unordered_map<string,bool> have_id;
unordered_map<string,int> name_id;
int main()
{
int n;
cin>>n;
double grade;
char id[7];
string name;
for(int i=0;i<n;i++){
scanf("%s%lf",id,&grade);
cin>>name;
transform(name.begin(),name.end(),name.begin(),::tolower);
if(!have_id[name]){
name_id[name] = number++;
have_id[name] = true;
}
int cur = name_id[name];//映射到已经开辟了空间的结构体数组中去
//这远比使用unorderde_map遇到一个string就开辟一个空间,节省时间
if(id[0] == 'T') grade *= 1.5;
else if(id[0] == 'B') grade /= 1.5;
total[cur].cnt++;
total[cur].tws += grade;
total[cur].name = name;
}
sort(total.begin(),total.begin()+number);
cout<<number<<endl;
int r = 1;
for(int i=0;i<number;i++){
if(i&&((int)total[i].tws != (int)total[i-1].tws)) r = i + 1;
cout<<r<<' '<<total[i].name<<' '<<(int)total[i].tws<<' '<<total[i].cnt<<endl;
}
return 0;
}
使用这个思路,再看一下提交结果
总结
可以看到这次,4、5测试点是没有超时,并且,前4个测试点的用时与上一种思路的用时差不多,都是一个数量级,也就是说,当数据量较少时,可以使用unordered_map来建立映射,但是当数据量巨大时,使用unordered_map < string,XXX > 来建立从string到其他类型XXX的数据映射,是比较费时的,这也与本题相关,可能后两个测试点,不断的有新的学校,在第一种思路中,所有从string到其他数据类型的映射都要不断地插入新的数据,这也就使得需要的内存空间确实是按需开辟,但是,在卡时间的oj上,这涉及到反复插入数据,尤其key为string,肯定比普通的int类型更费时间。
在第二种思路中,已经提前建立了结构体数组,只需要将学校姓名name映射到结构体数组的对应位置,进行数据修改即可,这个时间复杂度是O(1),肯定要比来一个尚未出现的数据就从新插入要节省时间。
本题告诉我,使用unordered_map<string,XXXX> 确实是非常方便
但是,其中的映射是一个一个的进行插入的
数据量巨大时,是相当耗时间的。数据量不大时,可以这么做
而最好是,建立string->int的映射。
因为数组是已经开辟好的,只是往里面进行填充,而不是用到了才开辟空间