1073 多选题常见计分法 (20 分)
题目
批改多选题是比较麻烦的事情,有很多不同的计分方法。有一种最常见的计分方法是:如果考生选择了部分正确选项,并且没有选择任何错误选项,则得到 50% 分数;如果考生选择了任何一个错误的选项,则不能得分。本题就请你写个程序帮助老师批改多选题,并且指出哪道题的哪个选项错的人最多。
输入格式:
输入在第一行给出两个正整数 N(≤1000)和 M(≤100),分别是学生人数和多选题的个数。随后 M 行,每行顺次给出一道题的满分值(不超过 5 的正整数)、选项个数(不少于 2 且不超过 5 的正整数)、正确选项个数(不超过选项个数的正整数)、所有正确选项。注意每题的选项从小写英文字母 a 开始顺次排列。各项间以 1 个空格分隔。最后 N 行,每行给出一个学生的答题情况,其每题答案格式为 (选中的选项个数 选项1 ……),按题目顺序给出。注意:题目保证学生的答题情况是合法的,即不存在选中的选项数超过实际选项数的情况。
输出格式:
按照输入的顺序给出每个学生的得分,每个分数占一行,输出小数点后 1 位。最后输出错得最多的题目选项的信息,格式为:错误次数 题目编号(题目按照输入的顺序从1开始编号)-选项号。如果有并列,则每行一个选项,按题目编号递增顺序输出;再并列则按选项号递增顺序输出。行首尾不得有多余空格。如果所有题目都没有人错,则在最后一行输出 Too simple。
输入样例 1:
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) (3 b d e) (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) (1 c) (4 a b c d)
输出样例 1:
3.5
6.0
2.5
2 2-e
2 3-a
2 3-b
输入样例 2:
2 2
3 4 2 a c
2 5 1 b
(2 a c) (1 b)
(2 a c) (1 b)
输出样例 2:
5.0
5.0
Too simple
思路
这题和1058感觉有点相似,不过这里属实复杂了,1058用的投机取巧的方式判断对错,这题还要具体统计每个选项的情况,属实不能投机了,只能请柳神了!!
这题的核心就在于用什么方式判断正误,并且能够获取每个选项的对错情况这里用了 ^
来判断是否完全正确,用 |
来判断是没有全对还是选错了,用 &
来判断每一个选项的答题情况
下面来具体分析一下每一步是如何实现的
模拟一个案例
正确选项是 a c d
三名学生给出的答案是
甲a c d
乙a c
丙a e
将正确答案和学生的答案映射成二进制数取a为低位,这边可以更具自己的思路决定顺序,只要不乱就好
那么正确答案acd映射成01101
甲同学 01101
乙同学 00101
丙同学 10001
^异或判断对错
这边的异或是对每一位进行异或,如果相等则为0,反之为1那么如果正确答案和同学们给出的答案相同的话,异或的到的答案就是0了,这样我们就很容易在程序中进行判断和处理了|或判断错误还是对一半
同理 01101 | 00101 或是对每一位进行或运算,如果有一个是1那么就是为1,这样的话只有同学的答案中出现了正确答案中没有的选项,或才会改变正确答案中的值,如果是漏选的话,并不影响正确答案的值,那么我就就可以通过或完后的答案和正确答案进行比较,判断出是否出现错误的情况,但是或比并不能判断全对还是半对,所以需要先判断是否全对,在用或来判断,具体实现在代码中体现& 与判断每一位的情况
这里与的实现原理就更加好理解了,我们已经知道通过^异或之后如果出现有的位上出现1,那么说明这一位和正确答案不一致,也就代表着这个选项该同学选错了,我们就用1,2,4,8,16遍历异或后的结果找出选错的那些选项,并计数就可以了
知道如何判断对错的放法,那么剩下的是实现出来了,这题的变量还是很多的,非常容易出错,而且出错还不容易debug,一些步骤的已经在代码中注释了。
代码
#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
/*变量
n和m存人数和题目数
一个数组长度为题目数m存放正确答案 vector<int> trueopt(m)
一个数组长度为5 用来将选项abcde转译成二进制数的中间数组 hash【5】 5个元素分别 1 2 4 8 16 对于5位二进制数的权值
一个数组长度为题目数m的用来存放每道题的满分值 vector<int> fullscore(m)
一个二维数组 存放每道题的每个选项的错误次数 vector<vector<int> > cnt(m,vector<int>(5))
*/
int main(){
int n,m;
cin>>n>>m;
//开始读取题目的满分值和正确选项
char c;
int optnum,truenum;
int hash[5] = {1, 2, 4, 8, 16};
vector<int> trueopt(m),fullscore(m);
for(int i = 0;i < m;i++){
cin>>fullscore[i]>>optnum>>truenum;
for(int j = 0;j < truenum;j++){
cin>>c;
trueopt[i] += hash[c - 'a'];
}
}
//开始读取每个同学每道题的答题情况,并在读取过程中进行核对和计算题目错误次数
vector<vector<int> > cnt(m,vector<int>(5));
int temp,opt;
for(int i = 0;i < n;i++){
double grade = 0;
for(int j = 0;j < m;j++){
getchar();//读到每道题刚开始的时候要用的scanf的格式化输入过滤掉( 这时候必须要将前面的一个回车或者空格吃掉不然会读错
scanf("(%d",&temp);
opt = 0;
for(int k = 0;k < temp;k++){
scanf(" %c)",&c);
opt += hash[c - 'a'];
}
int el = opt ^ trueopt[j];
if(el){//不完全正确
if((opt | trueopt[j]) == trueopt[j]){
grade += fullscore[j] * 1.0 / 2;
}
for(int k = 0;k < 5;k++){
if(el & hash[k]){//el的每一位如果是1的话就代表这一位所代表的选项错了,所以用hash遍历el找出错误的选项
cnt[j][k]++;
}
}
}
else{
grade += fullscore[j];
}
}
printf("%.1f\n", grade);
}
//找到错误最多的那个选项
int maxcnt = 0;
for(int i = 0;i < m;i++){
for(int j = 0;j < 5;j++){
maxcnt = cnt[i][j] > maxcnt ? cnt[i][j] : maxcnt;
}
}
if(maxcnt == 0)
cout<<"Too simple"<<endl;
else{
for(int i = 0;i < m;i++){
for(int j = 0;j < 5;j++){
if(cnt[i][j] == maxcnt)
printf("%d %d-%c\n",maxcnt,i+1,'a'+j);
}
}
}
return 0;
}