今天做了两个练习不定长数组的问题,基于vector实现,但是很类似的两个提采用了截然不同的处理方法,值得深思。
A1039
-
题目大意:
- N个学生,K⻔课,给出选择每⻔课的学生姓名,最后对于给出的N个学生的姓名的选课情况进行询问,要求按顺序给出每个学生的选课情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yFlT7c5S-1613402047976)(assets/1613401086404.png)]
-
注意点
- 学生名字对应选课代号可以用map<string,vector< Int> >,但本题数据量很大,不能用map,只能把学生名字转化成id,用hash存储。(对于本题来说,vector和map也只有这点差别)
- 注意字母转化成int时的算法:n=n*26+str[i]-‘A’.
-
代码
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
int getid(char *name) { //将字符串转换成hash的算法
int id = 0;
for(int i = 0; i < 3; i++)
id = 26 * id + (name[i] - 'A');
id = id * 10 + (name[3] - '0');
return id;
}
const int maxn = 26 * 26 * 26 * 10 + 10;
vector<int> v[maxn]; //用vector保存hashtable
int main() {
int n, k, no, num, id = 0;
char name[5];
scanf("%d %d", &n, &k);
for(int i = 0; i < k; i++) {
scanf("%d %d", &no, &num);
for(int j = 0; j < num; j++) {
scanf("%s", name);
id = getid(name);
v[id].push_back(no);
//根据名字得到hash的key,在后面保存对应的课程号
}
}
for(int i = 0; i < n; i++) {
scanf("%s", name);
id = getid(name);
sort(v[id].begin(), v[id].end());
printf("%s %lu", name, v[id].size());
for(int j = 0; j < v[id].size(); j++)
printf(" %d", v[id][j]);
printf("\n");
}
return 0;
}
A1047
- 题目大意
- 和上题相反,给出选课人数和课程数目,给出每个学生的选课情况,让你根据课程编号依次输出选这门课的学生名字,按字母序排列。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c8tF7xE0-1613402047983)(assets/1613401225233.png)]
4、5的输出略
- 注意点
- 因为题目信息是按照学生名字给出的,则可以把学生设置成id号,另建char数组记录学生名字和id对应。
- 设置vector< int >course [maxn] 数组,数组下标表示课程id,容器内部表示选当前这门课的学生id。
- 题目要求按照字母序排列,可以自定义cmp函数,交换容器内部元素的位次,而不改变id和name的对应规则。
- 代码
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
int N,K;
int n,k=0;
char name[40010][5]; //用来存放名字,每个名字对应一个编号即数组的行号
vector<int> course[2510]; //记录选每门课的学生对应的编号
//对vector排序,输入参数是vector<type>的type类型参数
bool cmp(int c1,int c2){ //用strcmp进行字典序排序
return strcmp(name[c1],name[c2])<0;
}
int main(){
scanf("%d %d",&N,&K);
for(int i=0;i<N;i++){
scanf("%s %d",name[i],&n);
for(int j=0;j<n;j++){
scanf("%d",&k);
course[k].push_back(i);
}
//sort不能放在这里,因为那个course没处理完,还可能再次改变
}
for(int i=1;i<=K;i++){
printf("%d %d\n",i,int(course[i].size()));
sort(course[i].begin(),course[i].end(),cmp);//在输出前按字典序排序
for(int j=0;j<course[i].size();j++){
printf("%s\n",name[course[i][j]]);
}
}
return 0;
}
这两个题目相当类似,为什么A1039选择了将name对应的字符串转换成hash,而A1047利用数组下标对name进行编号呢?
这类题最难处理的就是如何将字符串转换成整数,因为转换成整数的话无论是数组下标直接访问,还是vector的访问,都能做到在O(1)时间找到对应的元素。事实上,hash的方法就是基于此思想。矛盾点在于,是直接用char二维数组保存name字符串集,得到这个下标,还是采用hash的方法,将每个字符串对应一个整数,再对应一个数组对应下标的位置。
关键在于,name这个字符串,更具体的说是全部选课的人的name的这个字符串集是什么时候全部得到的。
对于问题A1039,是根据选某门课程的人名输入的,也就是在输入阶段,如果不进行任何处理,我们是无法直接得到这个set的(name字符串集)。我们首先要明确,为了更短的处理时间,要尽可能一边读入数据一边处理数据。如果使用直接下标保存的方法,则每读入一次数据,现需要遍历一边检查这个name字符串是不是已经被保存在数组中了,最后得到的必须是一个没有重复元素的集合。从理论上讲也是行得通的。但是A1039采用hash的方法,使得每个name都对应唯一的数组下标,根本不会发生检查是否已经保存的问题。
而对于问题A1047,因为在一边读入的过程中,name字符串集已经得到,因此每到一个新的name直接保存在数组中并分配一个下标即可,无需检查。
p.s 关于A1039的分析在自己实际去尝试一下这个寻找的方法以后,发现了更本质的问题。即:最后output的时候,是根据下标输出字符串,还是根据字符串输出下标对应位置的区别。对于A1039,很明显input的最后一行给了字符串顺序,因此是根据字符串输出下标对应位置,这种时候只能采用hash的方法,将字符串作为key直接实现O(1)的查找输出;而对于A1047,是明显按12345的课程号顺序输出字符串,可以将课程号和下标联系起来。