批改多选题是比较麻烦的事情,有很多不同的计分方法。有一种最常见的计分方法是:如果考生选择了部分正确选项,并且没有选择任何错误选项,则得到 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 <cstdio>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <vector>
using namespace std;
struct Problem {
int no; //题目编号
int score; //题目分数
set<char> right_ans; //正确选项集合
map<char, int> wrong_set; //出现学生选错的选项频次
Problem(int no, int score, int right_num)
: no(no), score(score) {
char c;
for (int i = 0; i < right_num; ++i) {
cin >> c;
right_ans.insert(c);
}
}//构造函数将正确选项填入集合
};
struct Student {
double score; //学生分数
vector<set<char>> solution; //学生的解体,是集合数组,对样例1而言,它就是四个集合构成的数组
};
//判断两个集合是否相等
bool setEqual(const set<char>& stu, const set<char> pro) {
if (stu.size() != pro.size()) return false;
for (auto it1 = stu.begin(), it2 = pro.begin(); it1 != stu.end();
++it1, ++it2) {
if (*it1 != *it2) return false;
}
return true;
}
//当学生解题没拿到满分时,进入这个函数,传的都是引用,里面修改可以影响到外面
void deal(Student& stu, int idx, Problem& pro) {
bool flag = true; //标志是否有错选
//学生选了的选项正确答案里却没有,该题肯定不得分
for (auto its = stu.solution[idx].begin(); its != stu.solution[idx].end();
++its) {
if (pro.right_ans.find(*its) == pro.right_ans.end()) {
pro.wrong_set[*its]++; //错误计数+1
flag = false; //这题没分了
}
}
//正确答案里有而学生没选,有可能得一半分,前提是没有错选,当然没选的选项的错误计数也要+1
for (auto itp = pro.right_ans.begin(); itp != pro.right_ans.end(); itp++) {
if (stu.solution[idx].find(*itp) == stu.solution[idx].end()) {
pro.wrong_set[*itp]++; //错误计数+1
}
}
if (flag) stu.score += pro.score * 1.0 / 2;
}
int main() {
int n, m, score, all_ans, right_num;
vector<Problem> v; //存放所有题目
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
cin >> score >> all_ans >> right_num;
Problem t = Problem(i, score, right_num);
v.emplace_back(t);
}
//至此,题目初始化完成
vector<Student> vs;
vs.resize(n); //强行改变大小,为了能直接下标访问修改内容,这样就不会用类似push_back的操作
for (int i = 0; i < n; ++i) vs[i].solution.resize(m); //同理,每个vs成员的solution也是vector,做类似操作
string line;
getchar(); //上面题目信息输入完后会遗留一个空格,吸掉
for (int i = 0; i < n; ++i) {
string line;
getline(cin, line); //获取一行字符串
int mark = 0; //这个标记的是当前处理的第几题
for (int j = 1; j < line.size();) { //从1开始,不处理左括号
int k;
if (line[j] >= '0' && line[j] <= '9') { //第一次进来,肯定是个数字
for (k = 1; k <= line[j] - '0'; k++) {
vs[i].solution[mark].insert(line[j + 2 * k]); //隔两个字符一个选项,插入进第i个学生的第mark和问题题解里
}
}
j += 2 * k - 1; //注意k,这个时候j刚好跳到右括号,或许也j=j+2*(k-1)+1更好理解
if (j != line.size() - 1) { //不是最后一个右括号,mark+1代表处理下一个题,j+3跳到下一个题解代表几个选项的数字
j += 3;
mark++;
}
else
break;
}
}
for (int i = 0; i < n; ++i) { //第i个学生
for (int j = 0; j < m; j++) { //第j个问题
if (setEqual(vs[i].solution[j], v[j].right_ans)) //集合相等代表做对了
vs[i].score += v[j].score;
else { //不等进去处理,传入当前学生,批改的题目序号,以及这道题的信息。
deal(vs[i], j, v[j]);
}
}
}
//打印学生分数
for (int i = 0; i < n; ++i) {
printf("%.1lf\n", vs[i].score);
}
int max_wrong = 0;
//求最大错误选项计数
for (int i = 0; i < m; ++i) {
for (auto it = v[i].wrong_set.begin(); it != v[i].wrong_set.end();
it++) {
if (it->second > max_wrong) {
max_wrong = it->second;
}
}
}
if (max_wrong == 0) {
printf("Too simple");
return 0;
}
//基本和上面那一段一模一样,一共两次遍历
for (int i = 0; i < m; ++i) {
for (auto it = v[i].wrong_set.begin(); it != v[i].wrong_set.end();
it++) {
if (it->second == max_wrong) {
printf("%d %d-%c\n", max_wrong, i + 1, it->first);
}
}
}
return 0;
}
总结:
1、写了一个下午,其中半个下午理解错了题意,不是求错的最多的题目,而是求错的最多的选项,就拿第三个题说,正确答案bc,学生1写ac,a作为错误选项计数+1;学生2写ab,a作为错误选项+1,c没选,也要把c的错误记数+1,学生3写c,同理b的错误计数+1,所以有 “2 3-a 2 3-b” 的输出结果
2、50行以上的代码我就hold不住了,也做不到代码精简,越写越长还不对,所以我的方案是尽可能解耦,保证思路清晰的情况下一点点写。如果我不写deal函数的话,就有三层for循环,不是很直观。
3、这题怎么说也得25吧,20太不给面子了