美文网首页
1073 多选题常见计分法 (20 分)

1073 多选题常见计分法 (20 分)

作者: 蜜糖_7474 | 来源:发表于2019-04-25 20:43 被阅读0次

批改多选题是比较麻烦的事情,有很多不同的计分方法。有一种最常见的计分方法是:如果考生选择了部分正确选项,并且没有选择任何错误选项,则得到 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太不给面子了

相关文章

网友评论

      本文标题:1073 多选题常见计分法 (20 分)

      本文链接:https://www.haomeiwen.com/subject/npuagqtx.html