目录
1058:选择题
批改多选题是比较麻烦的事情,本题就请你写个程序帮助老师批改多选题,并且指出哪道题错的人最多。
输入格式:
输入在第一行给出两个正整数 N(≤ 1000)和 M(≤ 100),分别是学生人数和多选题的个数。随后 M 行,每行顺次给出一道题的满分值(不超过 5 的正整数)、选项个数(不少于 2 且不超过 5 的正整数)、正确选项个数(不超过选项个数的正整数)、所有正确选项。注意每题的选项从小写英文字母 a 开始顺次排列。各项间以 1 个空格分隔。最后 N 行,每行给出一个学生的答题情况,其每题答案格式为 (选中的选项个数 选项1 ……)
,按题目顺序给出。注意:题目保证学生的答题情况是合法的,即不存在选中的选项数超过实际选项数的情况。
输出格式:
按照输入的顺序给出每个学生的得分,每个分数占一行。注意判题时只有选择全部正确才能得到该题的分数。最后一行输出错得最多的题目的错误次数和编号(题目按照输入的顺序从 1 开始编号)。如果有并列,则按编号递增顺序输出。数字间用空格分隔,行首尾不得有多余空格。如果所有题目都没有人错,则在最后一行输出 Too simple
。
输入样例:
3 4
3 4 2 a c
2 5 1 b
5 3 2 b c
1 5 4 a b d e
(2 a c) (2 b d) (2 a c) (3 a b e)
(2 a c) (1 b) (2 a b) (4 a b d e)
(2 b d) (1 e) (2 b c) (4 a b c d)
输出样例:
3
6
5
2 2 3 4
代码长度限制:
16 KB
时间限制:
300 ms
内存限制:
64 MB
思路:
1.选择题结构体
这道题看起来非常的复杂,首先我们需要写一个关于构建选择题的结构,这个结构需要一些什么呢?首先就是这个选择题的编号,我们用b来表示.
然后就是这个选择题做对了之后的分数,我们用f来表示.
还有就是这个选择题的选项总数,我们可以用s来进行表示.
之后就是这个选择题里面正确选项的数量,我们用z来进行表示.
在之后就是这个选择题里面正确选项是什么,定义一个char类型的内存空间,用zfc来进行表示.
在然后就是这个选择题被做错了的次数,我们可以用c来进行表示.
最后我们可以定义一个初始化函数init,函数参数有_f,_s,_z,_zfc,_b,函数里面呢首先就给zfc分配一片长度为_z的内存空间,然后用下标将_zfc赋值到zfc里面.然后其他的参数都赋值过去,我们还需要将c初始化为0,代表刚开始有0个人做错这道选择题.
1.2 选择题结构体代码
struct xz{ //选择题的结构体
int b; //这个选择题的编号
int f; //这个选择题做对过后获得的分数
int s; //这个选择题的选项总数
int z; //这个选择题的正确选项数量
char *zfc; //这个选择题的正确选项
int c; //这个选择题有多少个人做错
void init(int _f,int _s,int _z,char a[],int _b){ //初始化函数
zfc=new char[_z]; //分配一个长度为_z的内存空间
for(int i=0;i<_z;i++) //按下标赋值
zfc[i]=a[i]; //每一个字符都进行赋值
f=_f; //分数赋值
s=_s; //选项总数赋值
z=_z; //正确选项数量赋值
b=_b; //选择题编号赋值
c=0; //将统计做错次数的计数器初始化为0
}
};
2.判断选择题是否做对函数
我们可以再写一个判断这个选择题是否做对的函数.
函数参数为一个选择题结构xz a,学生在选择题里面选的选项的数组b,以及这个数组b的长度n.
首先我们要判断a.z(这个选择题正确选项的个数)是否等于n,如果不等于,那就说明学生肯定选错了,返回false,毕竟选的数量都不对,如果等于的话,进行下一步判断.
依次遍历a.zfc和b这两个字符串,如果有一个选错,那么就返回false,代表学生选错了,如果遍历一遍都没有找到不同的地方,就返回true,代表学生选对了.
2.1 判断选择题是否做对函数代码:
bool pd(xz a,int n,char b[]){ //判断选择题是否做对函数代码
if(a.z!=n) //看学生选的选项个数和正确选项个数是否不同
return false; //如果不同,就代表学生选错了
else{ //如果相同,进行下一次判断
for(int i=0;i<n;i++) //依次遍历比较
if(a.zfc[i]!=b[i]) //如果找到一个不相同的
return false; //就代表学生选错了
return true; //没有找到不相同的地方,就代表学生做对了
}
}
3.选择题的输入
我们首先输入的是n(学生个数)和m(选择题个数),然后进行m次输入一个选择题的内容的循环,期间,定义三个整数f(这个选择题做对后的分数),x(这个选择题的选项总数),y(这个选择题的正确选项数量),输入过后,我们在定义一个char类型的数组s,长度为y(代表正确的选项有哪些).然后进行y次输入s[j],之后我们可以用xz这个结构体的初始化函数init进行一下初始化!
3.1 选择题的输入代码:
int n,m; //n为学生数量,m为选择题的数量
cin>>n>>m; //输入学生数量和选择题的数量
int sum[n]={0}; //每个学生获得的分数
xz a[m]; //m个选择题的信息
for(int i=0;i<m;i++){ //输入m个选择题的信息
int f,x,y; //f是这个选择题做对后获得的分数,x是这个选择题的选项总数,y是这个选择题的正确选项数目
cin>>f>>x>>y; //输入选择题的分数,选项总数,正确选项总数
char s[y]; //定义一个长度为y的数组,存储的是y个正确的选项
for(int j=0;j<y;j++) //依次输入y个正确的选项
cin>>s[j]; //输入第j个正确的选项
a[i].init(f,x,y,s,i); //利用初始化函数进行赋值
}
4.学生做选择题的输入
因为题目的特殊输入要求,需要有括号在旁边,我们就需要定义一个新的char类型变量,用于存储左括号和右括号,每一个学生做完之后,输入的数据我们可以不用保留(也就是说我们不用定义一个三维的数组,只需要在for循环之中定义新的变量就可以了),然后在定义一个整数d(代表学生做这道题选的选项个数),输入了左括号和d之后,我们在定义一个长度为d的char类型数组l,里面存储的就是学生选的选项是哪些.
之后我们进行d次循环,依次输入,把l给输入了之后,我们在输入一个右括号,这些输入完了之后,就可以进行判断学生是否做对,学生获得的分值,这道选择题的记录错误人数的计数器要不要了.(之后再说)!
4.1 学生做选择题的输入代码
while(n--){ //n个学生的试卷
for(int i=0;i<m;i++){ //做m道选择题的答案
char c; //刚开始的括号用这个输入
int d; //这个选择题学生选的个数
cin>>c>>d; //输入左括号和选择题学生选的个数
char l[d]; //定义一个长度为d的字符串,存储的是学生选的选项
for(int j=0;j<d;j++) //依次输入学生选的d个选项
cin>>l[j]; //输入学生选的第j个选项
cin>>c; //输入右括号
/*
......
中间是判断做题是否正确加分的代码......
......
*/
}
/*
......
中间是判断做题是否正确加分的代码......
......
*/
}
5.批改学生做的选择题
将每一个学生做的选择题选项输入了之后,就该批改学生做的选择题了(之前吗定义了一个sum数组,就是用来存储这些学生获得的分数的),之前我们也写了一个判断是否做对了的函数pd,我们现在就可以运用进来.
首先定义一个bool类型的变量f,将其赋值为pd(a[i],d,l)的返回值,如果为真(true)的话,代表做对了,将sum[x(x为最开始定义的一个下标,最大为n,代表着学生的编号)]的值在加上这个选选择题的分数(a[i].f).
如果!f(取反f的值,比如说,如果f为true,取反之后为false,反之,如果f为false,取反之后就成了tue)的值为真(true)的话,代表学生这道选择题做错了,我们只需要将这个选择题记录错题人数(a[i].c)的变量进行+1的操作.
之后输入完一个学生的所有做选择题的for循环之后,将记录学生当前的下标的x进行+1操作(毕竟下一次就是下一个同学的卷子了).
5.1 批改学生做的选择题代码:
while(n--){ //n个学生的试卷
for(int i=0;i<m;i++){ //做m道选择题的答案
char c; //刚开始的括号用这个输入
int d; //这个选择题学生选的个数
cin>>c>>d; //输入左括号和选择题学生选的个数
char l[d]; //定义一个长度为d的字符串,存储的是学生选的选项
for(int j=0;j<d;j++) //依次输入学生选的d个选项
cin>>l[j]; //输入学生选的第j个选项
cin>>c; //输入右括号
bool f=pd(a[i],d,l); //判断是否做对了这道选择题
if(!f) //如果取反后为true,就代表f是false,学生这道选择题做错了
a[i].c++; //将记录错题人数的计数器c进行+1操作
else //如果取反后不是true,就代表f是true,学生这道选择题做对了
sum[x]+=a[i].f; //将记录学生分数的数组加上这个选择题的分数
}
x++; //学生下标增加
}
6. 输出每一个学生获得的总分数
经过上述批改学生做选择题卷子的操作之后,sum这个数组里面就存的是每一个学生做选择题获得的总分数,我们就只需要for循环输出一遍就行了.
6.1 输出每一个学生获得的总分数代码:
for(int i=0;i<x;i++) //将每一个学生获得的总分数输出出来
cout<<sum[i]<<endl; //输出编号为i的学生的总分数
7.统计出错最多的选择题
7.1 结构体排序规则1
我们首先需要进行一个排序,为了简单一些,吗可以用C++STL库系统默认提供的sort排序函数,但是我们需要排序的是一个结构,系统不知道我们的结构要怎么排序,所以会报错.
现在吗就需要写一个结构体排序规则的函数,我们呢需要出错最多的选择题,然后输出一次出错次数,接着输出他们的编号,我们的规则就可以制定为按照两个选择题xz结构体中的变量c进行比较,谁的大,谁就在前面.
7.1.1 结构体排序规则1代码:
bool cmp(xz a,xz b){ //参数是两个结构体
return a.c>b.c; //比较这两个选择题错题数量谁多
}
7.2 结构体排序规则2
题目要求说了,可能有多个错题数量相同的选择题,按照他们的编号进行从小到大依次输出,经过第一次排序过后,多个错题数量相同的选择题的编号可能不是从小到大,这样我们就需要在进行一次按照编号来排序了.
7.2.1 统计有多少个错题数量相同的选择题
我们要排序,就要给出排序的长度,第二次排序,排序的长度就是错题数量相同的选择题数量我们可以怎么求呢?
我们知道,经过第一次排序过后,下标为0的选择题错题的数量绝对是最多的,我们就可以进行循环遍历,从0开始,到m-1,中间怎么判断呢?可以定义一个计数器vum初始化为0,如果相等就将计数器+1,如果不相等,那么后面的数字肯定也不相等,就可以直接break退出.
7.2.2 统计有多少个错题数量相同的选择题代码:
for(int i=0;i<m;i++){ //统计有多少个错题数量相同的选择题
if(a[0].c==a[i].c) //如果找到相同的
vum++; //就将计数器进行+1操作
else //如果找到一个不相同的,那就说明后面的肯定不会相同
break; //就可以直接退出了
}
经过上述操作之后,知道了第二次排序的长度应该是vum,现在我们可以写一个第二次排序规则的函数了,我们是根据编号比较的,就可以按照两个选择题xz结构中的变量b来进行比较,谁的编号小,谁就往前面去.
7.2.3 结构体排序规则2代码:
bool cmp2(xz a,xz b){ //参数是两个结构体
return a.b<b.b; //比较这两个选择题的编号谁的更小
}
8.输出错题数量最多的选择题编号
因为题目有特殊要求,行末没有多余的空格,所以需要进行一次特判,如果不是最后一行,就多输出一个空格,如果是,就多输出一个换行键(<<endl;)!
8.1 输出错题数量最多的选择题编号代码:
sort(a,a+m,cmp); //按照每一个选择题的错题次数排序
for(int i=0;i<m;i++){ //统计有多少个错题数量相同的选择题
if(a[0].c==a[i].c) //如果找到相同的
vum++; //就将计数器进行+1操作
else //如果找到一个不相同的,那就说明后面的肯定不会相同
break; //就可以直接退出了
}
sort(a,a+vum,cmp2); //按照每一个错题数量相同的选择题的编号进行排序
cout<<a[0].c<<" "; //输出最多的错题数量
for(int i=0;i<vum;i++){ //输出错题数量最多的选择题的编号
if(i!=(vum-1)) //如果不是最后一个
cout<<a[i].b+1<<" "; //输出空格
else //如果是最后一个
cout<<a[i].b+1<<endl; //不输出空格,输出换行
}
总代码:
#include<bits/stdc++.h>
using namespace std;
struct xz{ //选择题的结构体
int b; //这个选择题的编号
int f; //这个选择题做对过后获得的分数
int s; //这个选择题的选项总数
int z; //这个选择题的正确选项数量
char *zfc; //这个选择题的正确选项
int c; //这个选择题有多少个人做错
void init(int _f,int _s,int _z,char a[],int _b){ //初始化函数
zfc=new char[_z]; //分配一个长度为_z的内存空间
for(int i=0;i<_z;i++) //按下标赋值
zfc[i]=a[i]; //每一个字符都进行赋值
f=_f; //分数赋值
s=_s; //选项总数赋值
z=_z; //正确选项数量赋值
b=_b; //选择题编号赋值
c=0; //将统计做错次数的计数器初始化为0
}
};
bool cmp(xz a,xz b){ //参数是两个结构体
return a.c>b.c; //比较这两个选择题错题数量谁多
}
bool cmp2(xz a,xz b){ //参数是两个结构体
return a.b<b.b; //比较这两个选择题的编号谁的更小
}
bool pd(xz a,int n,char b[]){ //判断选择题是否做对函数代码
if(a.z!=n) //看学生选的选项个数和正确选项个数是否不同
return false; //如果不同,就代表学生选错了
else{ //如果相同,进行下一次判断
for(int i=0;i<n;i++) //依次遍历比较
if(a.zfc[i]!=b[i]) //如果找到一个不相同的
return false; //就代表学生选错了
return true; //没有找到不相同的地方,就代表学生做对了
}
}
int main(){
int n,m; //n为学生数量,m为选择题的数量
cin>>n>>m; //输入学生数量和选择题的数量
int sum[n]={0}; //每个学生获得的分数
xz a[m]; //m个选择题的信息
for(int i=0;i<m;i++){ //输入m个选择题的信息
int f,x,y; //f是这个选择题做对后获得的分数,x是这个选择题的选项总数,y是这个选择题的正确选项数目
cin>>f>>x>>y; //输入选择题的分数,选项总数,正确选项总数
char s[y]; //定义一个长度为y的数组,存储的是y个正确的选项
for(int j=0;j<y;j++) //依次输入y个正确的选项
cin>>s[j]; //输入第j个正确的选项
a[i].init(f,x,y,s,i); //利用初始化函数进行赋值
}
int x=0,vum=0;
while(n--){ //n个学生的试卷
for(int i=0;i<m;i++){ //做m道选择题的答案
char c; //刚开始的括号用这个输入
int d; //这个选择题学生选的个数
cin>>c>>d; //输入左括号和选择题学生选的个数
char l[d]; //定义一个长度为d的字符串,存储的是学生选的选项
for(int j=0;j<d;j++) //依次输入学生选的d个选项
cin>>l[j]; //输入学生选的第j个选项
cin>>c; //输入右括号
bool f=pd(a[i],d,l); //判断是否做对了这道选择题
if(!f) //如果取反后为true,就代表f是false,学生这道选择题做错了
a[i].c++; //将记录错题人数的计数器c进行+1操作
else //如果取反后不是true,就代表f是true,学生这道选择题做对了
sum[x]+=a[i].f; //将记录学生分数的数组加上这个选择题的分数
}
x++; //学生下标增加
}
for(int i=0;i<x;i++) //将每一个学生获得的总分数输出出来
cout<<sum[i]<<endl; //输出编号为i的学生的总分数
sort(a,a+m,cmp); //按照每一个选择题的错题次数排序
for(int i=0;i<m;i++){ //统计有多少个错题数量相同的选择题
if(a[0].c==a[i].c) //如果找到相同的
vum++; //就将计数器进行+1操作
else //如果找到一个不相同的,那就说明后面的肯定不会相同
break; //就可以直接退出了
}
sort(a,a+vum,cmp2); //按照每一个错题数量相同的选择题的编号进行排序
cout<<a[0].c<<" "; //输出最多的错题数量
for(int i=0;i<vum;i++){ //输出错题数量最多的选择题的编号
if(i!=(vum-1)) //如果不是最后一个
cout<<a[i].b+1<<" "; //输出空格
else //如果是最后一个
cout<<a[i].b+1<<endl; //不输出空格,输出换行
}
return 0; //结束
}
该算法的复杂度:
1.时间复杂度
我们先来计算时间复杂度,可以看出来,我们这里的每一个操作都涉及了预处理(在输入的时候进行处理).
首先,输入每一个选择题的信息的时候,进行了m次输入,里面有一次init的初始化函数操作,init函数里面有一个for循环,复杂度是O(Z(Z是正确选项个数,不超过5)),那么输入每一个选择题信息的复杂度为O(ZM).
然后来看批改每一个学生的试卷,有n个同学,每一个同学做m个选择题,复杂度是O(NM),中间,还执行了一次判断选择题是否做对的函数,复杂度为O(logD(d为每一个选择题学生选的个数,最大也是不超过5)),所以批改同学试卷发时间复杂度为O(NMlogD);
最后是输出时候的复杂度,输出每一个同学的分数循环,复杂度为O(N(while循环过后,x的值等于n)) ,然后是进行第一次排序,复杂度为O(M^2(sort是冒泡排序)).之后进行统计有多少个有多少个最大错题数相等的选择题数量,时间复杂度为O(logM);之后又进行了一次冒泡排序,复杂度为O(logM^2).
所以说,我们的总复杂度是O(ZM+NMlogD+N+M^2+logM+logM^2).最后总结一下等于O(NMlogD+2M^2)(为了简略一下,忽略一下较为小的数),按照前面那个比较完整的时间复杂度,我们来计算一下最大时间复杂度是多少Z最大为5,M最大为100,N最大为1000,D最大为5,总复杂度最大为O(500+500000+1000+10000+100+10000)=O(521600).最大是五十二万一千六百,在160ms之前是算的出来的,所以不会超时.
2.空间复杂度
首先看xz这个结构的空间复杂度,最大大概为O(10),我们定义了M个这样的结构,空间复杂度为O(10M),然后又定义了数组sum,长度为n,所以它的空间复杂度是O(N).
所以他的整体复杂度应该是O(10M+N)(省略了一些较小的空间)!
总结:
这道题算是特别特别难的了,需要进行写特殊的结构体,以及多个结构体排序函数,还有分数记录和函数进行的判断.
题目链接:
推荐:
欢迎大家前往AcWing网站进行刷题,上课,该网站由yxc,y总建立!
AcWing一个专属于程序员的平台,为大家在漫漫的刷题之旅中,提供最优质的解答https://www.acwing.com/about/