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

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太不给面子了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352