题目链接:https://pintia.cn/problem-sets/994805260223102976/problems/994805263624683520
批改多选题是比较麻烦的事情,有很多不同的计分方法。有一种最常见的计分方法是:如果考生选择了部分正确选项,并且没有选择任何错误选项,则得到 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
最终AC代码如下:
#include <iostream> #include <vector> using namespace std; struct Problem{ double score; //分数 int len1, len2;//总选项个数 正确答案个数 vector<int> vi, re;//存选项 存错误选项 Problem(){ score = 0; len1 = len2 = 0; vi.clear(); re.clear(); } }; int main(){ int i, j, n, m, t1, t2, maxn=0; double sum; char c; Problem p; vector<Problem> vp; cin>>n>>m; for(i=0; i<m; i++){ p = Problem(); cin>>p.score>>t1>>t2; p.len1 = t1; p.len2 = t2; p.vi = vector<int>(t1, 0); p.re = vector<int>(t1, 0); for(j=0; j<t2; j++){ cin>>c; p.vi[c-'a'] = 1; } vp.push_back(p); } while(n--){ sum = 0; for(i=0; i<m; i++){ c = 'a'; while(c!='('){ scanf("%c", &c); } scanf("%d", &t1); p = Problem(); p.vi = vector<int>(vp[i].len1, 0); c = 'a'; while(c!=')'){ scanf("%c", &c); if(c>='a'){ p.vi[c-'a'] = 1; } } for(j=0; j<vp[i].len1; j++){ if(p.vi[j]&(vp[i]).vi[j]){ p.len2++; //记录正确选项 } if(p.vi[j]^(vp[i]).vi[j]){ (vp[i]).re[j]++; //记录选项的错误记录 if((vp[i]).re[j]>maxn){ maxn = (vp[i]).re[j]; //存储所有选项的最大错误记录 } } } if(p.len2==t1){ //表示没有错选 if(p.len2==vp[i].len2){ //全对 sum += vp[i].score; }else{ //漏选 sum += (0.5 * vp[i].score); } } } printf("%.1f ", sum); } if(maxn==0){ //表示没有选项被选错 printf("Too simple "); return 0; } for(i=0; i<m; i++){ for(j=0; j<vp[i].len1; j++){ if((vp[i]).re[j] == maxn){ printf("%d %d-%c ", maxn, i+1, 'a'+j); } } } return 0; }
哈哈,可以说AC掉这道题,是刷所有PAT乙级题目中最开心的一道了,当然也是最后AC掉的。最让人不可置信的,我居然只提交一次就AC了。咳咳,有点得意忘形了,言归正传。
这种题目,看着很复杂,以及各种奇葩的条件让人瞬间没了自信。但是,其实却不一定是最难的,因为只要照着题干把所有要求实现了,或者更夸张地说,只要把下面的测试用例通过了,那么一般在OJ上的测试用例也能通过。简而言之,没有埋在地下的坑点。
主要问题有:一、如何判断全对、漏选以及错选三种情况。二、如何记录错误最多的选项,并且还要按序输出。
下面举一个小例子说明:
#include <iostream> using namespace std; int main(){ int a[]={1,1,0,1}, b[]={1,0,1,0};//a[]为正确选项 b[]为学生选的选项 for(int i=0; i<4; i++){ //全对 a[i]&b[i]的结果中1的个数为k ,且k等于正确选项的个数 //漏选 a[i]&b[i]的结果中1的个数为k,且k等于学生所选的个数 cout<<"==&=="<<(a[i]&b[i])<<"...."; //错选或漏选选项 a[i]^b[i]的结果中1的个数为k,有k个错误选项 cout<<"==^=="<<(a[i]^b[i])<<endl; } return 0; }
对应的输出结果为:
==&==1....==^==0 ==&==0....==^==1 ==&==0....==^==1 ==&==0....==^==1
注:(a[]、b[]中,下标0、1....代表a、b....)
从打印的结果可以知道,a选项选对了,a[]与b[]项与的结果为1的时候,就记录选对的次数k。如果k的值与学生选择的选择个数(暂记为cnt)不相等,那么该题为错选(如上例中,cnt=2,k=1)。在k==cnt的前提下,如果k的值与正确选项的个数(暂记为ans),那么该题全对(上例中的ans=3);否则,该题无错选但是漏选。
如此一来,第一个头疼的问题就被解决了。对于第二个问题,则看下面的分析。
b、d是漏选的项,c是选错的项,可以知道只要是a[]与b[]取非结果为1的时候,此时该选项就是被错选的,便在记录错选的变量+1,并且用一个maxn来记录最大的错选次数。其中,maxn初值为0,也就是说对完所有学生的答题情况后,若maxn==0,则所有学生都得满分;否则,按序遍历所有问题的所选选项,在满足某选项的错误次数k==maxn时,则输出此时的情况。由于是按序遍历,因为解决了并列情况的排序问题。
其中,较为重要的一点是,将每一个 问题 都当做一个对象来处理,具体写程序时就是定义一个结构体变量。如下:
struct Problem{ double score; //分数 int len1, len2;//总选项个数 正确答案个数 vector<int> vi, re;//存选项 存错误选项 Problem(){ score = 0; len1 = len2 = 0; vi.clear(); re.clear(); } };
哈哈,如此一来,所有问题都迎刃而解了,最后就是一些逻辑的判断了。