洛谷 - P7426 [THUPC 2017] 体育成绩统计

题目链接

考察知识点

  • 算法 - 模拟

思路分析

一道丧心病狂的大模拟

依据题意模拟即可

时间复杂度

\(O(n+m)\)

C++ 代码

#include <map>       // 用于建立学号与学生索引的映射
#include <iostream>  // 用于输入输出操作
#include <vector>    // 用于存储每个学生的阳光长跑记录
using namespace std;

const int N = 1e4 + 10;  // 学生最大数量(10010),防止数组越界
map<int, int> h;         // 映射表:key为学号,value为学生在数组中的索引(便于快速查找学生)

/*
体育课总评成绩构成(满分100分):
- 体育课专项成绩:满分50分
- 长跑测试成绩:满分20分
- 「阳光长跑」成绩:满分10分
- 体质测试成绩:满分10分(通过为10分,未通过为0分)
- 「大一专项计划」成绩:满分10分
  - 出勤次数(阳光长跑+班级训练营):占5分
  - 期末检测成绩:占5分(直接由输入提供)
*/

// 学生信息结构体,存储单个学生的所有基础数据
struct student {
    int idx;             // 学号(唯一标识)
    int sex;             // 性别:1表示男,2表示女
    int PEScore;         // 体育课专项成绩(满分50分)
    int LongDisData;     // 期末长跑测试时间(单位:秒)
    int HealthScore;     // 体质测试成绩(10分或0分)
    int SpecialScore;    // 「大一专项计划」的期末检测成绩(满分5分)
    int ExerciseTimes;   // 参加「班级训练营」的次数(用于计算出勤分)
    int SunshineTimes;   // 有效「阳光长跑」的次数(用于计算阳光长跑成绩和出勤分)
} stu[N];  // 存储所有学生信息的数组

// 阳光长跑记录结构体,存储单次跑步的详细数据
struct Record {
    int date;       // 跑步日期(格式:假设为年月日,如20231001表示2023年10月1日)
    int Sa, Sb, Sc; // 开始时间:Sa=时,Sb=分,Sc=秒
    int Ea, Eb, Ec; // 结束时间:Ea=时,Eb=分,Ec=秒
    int a, b;       // 暂停时间:a=分,b=秒(总暂停时间为a*60 + b秒)
    int step;       // 跑步步数
    double dis;     // 跑步距离(单位:公里,后续会转为米)
};

vector<Record> recVector[N]; // 存储每个学生的所有阳光长跑记录:recVector[i]为第i个学生的记录列表

// 每个月的天数(非闰年),用于将日期转换为当年的第几天
const int month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int n, m; // n=学生数量,m=阳光长跑记录数量

// 将分钟和秒转换为总秒数(如a=1分,b=30秒 → 90秒)
int MinuteToSecond(int a, int b) {
    return a * 60 + b;
}

// 将日期转换为当年的第几天(如3月1日 → 31(1月)+28(2月)+1=60天)
// 假设日期格式为:前四位为年份(未使用),后四位为月日(如xxxxMMDD)
int YearToDay(int date) {
    // 提取月份和日期(从后四位中解析:MMDD)
    int n5 = date / 1000 % 10;  // 月份十位
    int n6 = date / 100 % 10;   // 月份个位
    int n7 = date / 10 % 10;    // 日期十位
    int n8 = date / 1 % 10;     // 日期个位
    int Month = n5 * 10 + n6;   // 月份(1-12)
    int Day = n7 * 10 + n8;     // 日期(1-31)
    int all = 0;
    // 累加当月之前所有月份的天数
    for (int i = 1; i <= Month - 1; i++) {
        all += month[i];
    }
    return all + Day; // 总天数=前几个月天数+当月日期
}

// 将时、分、秒转换为总秒数(如1时2分3秒 → 3600 + 120 + 3 = 3723秒)
int HourToSecond(int a, int b, int c) {
    return a * 3600 + b * 60 + c;
}

/*
检查单次阳光长跑记录是否有效,有效则计入阳光长跑次数
参数说明:
- date, Sa, Sb, Sc, Ea, Eb, Ec, a, b, step, dis:单次跑步记录的原始数据
- sex:学生性别(1=男,2=女)
- last_end:引用传递,记录上一次有效跑步的结束时间(用于检查时间间隔)
返回值:true=有效,false=无效
*/
bool check(int date, int Sa, int Sb, int Sc, int Ea, int Eb, int Ec, int a, int b, int step, double dis, int sex, long long &last_end) {
    // 1. 计算跑步开始和结束的总秒数(从当年1月1日0点0分0秒开始计算)
    int day = YearToDay(date); // 日期转换为当年的第几天
    // 开始时间总秒数 = (天数-1)*86400(一天秒数) + 当天时间秒数
    long long StartSecond = (day - 1LL) * 86400 + HourToSecond(Sa, Sb, Sc);
    // 结束时间总秒数(若跨天,结束时间可能大于86400,需加一天秒数)
    long long EndSecond = (day - 1LL) * 86400 + HourToSecond(Ea, Eb, Ec);
    if (EndSecond < StartSecond) { // 若结束时间在开始时间之前,说明跨天
        EndSecond += 86400;
    }

    // 2. 检查与上一次有效跑步的时间间隔是否≥6小时(6*3600秒)
    if (StartSecond - last_end < 6 * 3600) {
        return false;
    }

    // 3. 检查步数是否为正数(无效步数)
    if (step <= 0) {
        return false;
    }

    // 4. 检查步距是否合理(步距=距离/步数,单位:米/步,需≤1.5米)
    if (dis / step > 1.5) {
        return false;
    }

    // 5. 检查暂停时间是否≤4分30秒(270秒)
    int pause_seconds = MinuteToSecond(a, b); // 暂停时间总秒数
    if (pause_seconds > 270) {
        return false;
    }

    // 6. 检查跑步时长是否为正数(无效时长)
    long long duration = EndSecond - StartSecond; // 跑步总时长(秒)
    if (duration <= 0) {
        return false;
    }

    // 7. 检查跑步速度是否合理(2米/秒 ≤ 速度 ≤5米/秒,速度=距离/时长)
    double speed = dis / duration;
    if (speed < 2 || speed > 5) {
        return false;
    }

    // 8. 检查跑步距离是否达标(男生≥3000米,女生≥1500米)
    if (sex == 1) { // 男生
        if (dis < 3000) {
            return false;
        }
    } else { // 女生
        if (dis < 1500) {
            return false;
        }
    }

    // 所有条件均满足,更新上一次有效跑步的结束时间
    last_end = EndSecond;
    return true;
}

/*
根据性别和长跑测试时间(秒)计算长跑测试成绩(满分20分)
参数:
- sex:性别(1=男,2=女)
- sec:长跑测试时间(秒)
返回值:对应的分数(0-20分)
*/
int LongDisScoreJudge(int sex, int sec) {
    if (sex == 1) { // 男生评分标准(对应不同时间区间的分数)
        if (sec <= 12 * 60 + 30) return 20;   // ≤12'30'' → 20分
        else if (sec <= 13 * 60) return 18;    // ≤13'00'' → 18分
        else if (sec <= 13 * 60 + 30) return 16;// ≤13'30'' → 16分
        else if (sec <= 14 * 60) return 14;    // ≤14'00'' → 14分
        else if (sec <= 14 * 60 + 30) return 12;// ≤14'30'' → 12分
        else if (sec <= 15 * 60 + 10) return 10;// ≤15'10'' → 10分
        else if (sec <= 15 * 60 + 50) return 8; // ≤15'50'' → 8分
        else if (sec <= 16 * 60 + 30) return 6; // ≤16'30'' → 6分
        else if (sec <= 17 * 60 + 10) return 4; // ≤17'10'' → 4分
        else if (sec <= 18 * 60) return 2;     // ≤18'00'' → 2分
        else return 0;                         // >18'00'' → 0分
    } else { // 女生评分标准(对应不同时间区间的分数)
        if (sec <= 6 * 60 + 40) return 20;    // ≤6'40'' → 20分
        else if (sec <= 6 * 60 + 57) return 18; // ≤6'57'' → 18分
        else if (sec <= 7 * 60 + 14) return 16; // ≤7'14'' → 16分
        else if (sec <= 7 * 60 + 31) return 14; // ≤7'31'' → 14分
        else if (sec <= 7 * 60 + 50) return 12; // ≤7'50'' → 12分
        else if (sec <= 8 * 60 + 5) return 10;  // ≤8'05'' → 10分
        else if (sec <= 8 * 60 + 20) return 8;  // ≤8'20'' → 8分
        else if (sec <= 8 * 60 + 35) return 6;  // ≤8'35'' → 6分
        else if (sec <= 8 * 60 + 50) return 4;  // ≤8'50'' → 4分
        else if (sec <= 9 * 60) return 2;      // ≤9'00'' → 2分
        else return 0;                          // >9'00'' → 0分
    }
}

/*
根据有效阳光长跑次数计算「阳光长跑」成绩(满分10分)
参数:t=有效阳光长跑次数
返回值:对应的分数(0-10分)
*/
int SunshineScoreJudge(int t) {
    if (t >= 21) return 10;  // ≥21次 → 10分
    else if (t >= 19) return 9; // ≥19次 → 9分
    else if (t >= 17) return 8; // ≥17次 → 8分
    else if (t >= 14) return 7; // ≥14次 → 7分
    else if (t >= 11) return 6; // ≥11次 → 6分
    else if (t >= 7) return 4;  // ≥7次 → 4分
    else if (t >= 3) return 2;  // ≥3次 → 2分
    else return 0;              // <3次 → 0分
}

/*
计算「大一专项计划」中的出勤分(满分5分)
出勤次数=阳光长跑有效次数 + 班级训练营参加次数
参数:
- a=阳光长跑有效次数
- b=班级训练营参加次数
返回值:对应的出勤分(0-5分)
*/
int ChuQinScoreJudge(int a, int b) {
    int sum = a + b; // 总出勤次数
    if (sum >= 18) return 5;  // ≥18次 → 5分
    else if (sum >= 15) return 4; // ≥15次 → 4分
    else if (sum >= 12) return 3; // ≥12次 → 3分
    else if (sum >= 9) return 2;  // ≥9次 → 2分
    else if (sum >= 6) return 1;  // ≥6次 → 1分
    else return 0;                // <6次 → 0分
}

/*
根据总分数计算等级
参数:score=总分数(0-100分)
返回值:对应的等级字符串
*/
string GradeJudge(int score) {
    if (score >= 95) return "A";    // ≥95 → A
    else if (score >= 90) return "A-"; // ≥90 → A-
    else if (score >= 85) return "B+"; // ≥85 → B+
    else if (score >= 80) return "B";  // ≥80 → B
    else if (score >= 77) return "B-"; // ≥77 → B-
    else if (score >= 73) return "C+"; // ≥73 → C+
    else if (score >= 70) return "C";  // ≥70 → C
    else if (score >= 67) return "C-"; // ≥67 → C-
    else if (score >= 63) return "D+"; // ≥63 → D+
    else if (score >= 60) return "D";  // ≥60 → D
    else return "F";                   // <60 → F(不及格)
}

int main() {
    scanf("%d", &n); // 输入学生数量

    // 读取n个学生的基础信息
    for (int i = 1; i <= n; i++) {
        char sex, IsPass; // sex=性别('M'男/'F'女),IsPass=体质测试是否通过('P'通过/'N'未通过)
        int a, b;         // 用于存储长跑测试的分和秒(如a=12分,b=30秒)

        // 读取学号、性别、体育课专项成绩
        cin >> stu[i].idx >> sex >> stu[i].PEScore;
        // 读取长跑测试时间(格式:a'b'',如12'30'')
        scanf("%d\'%d\"", &a, &b);
        // 读取体质测试是否通过、专项计划期末检测成绩、班级训练营次数
        cin >> IsPass >> stu[i].SpecialScore >> stu[i].ExerciseTimes;

        // 建立学号到学生索引的映射(方便后续查找)
        h[stu[i].idx] = i;

        // 转换性别为数字(1=男,2=女)
        if (sex == 'M') {
            stu[i].sex = 1;
        } else {
            stu[i].sex = 2;
        }

        // 体质测试成绩:通过为10分,未通过为0分
        stu[i].HealthScore = (IsPass == 'P') ? 10 : 0;

        // 将长跑测试时间转换为总秒数(存储在LongDisData中)
        stu[i].LongDisData = MinuteToSecond(a, b);

        // 初始化阳光长跑有效次数为0
        stu[i].SunshineTimes = 0;
    }

    scanf("%d", &m); // 输入阳光长跑记录数量

    // 读取m条阳光长跑记录,并按学生分组存储
    for (int i = 1; i <= m; i++) {
        int idx; // 学生学号
        Record r; // 单次跑步记录

        // 读取记录信息:日期、学号、开始时间、结束时间、距离(公里)、暂停时间、步数
        // 时间格式:Sa:Sb:Sc(开始)、Ea:Eb:Ec(结束),暂停时间格式:a'b''
        scanf("%d %d %d:%d:%d %d:%d:%d %lf %d\'%d\" %d",
              &r.date, &idx,
              &r.Sa, &r.Sb, &r.Sc, &r.Ea, &r.Eb, &r.Ec,
              &r.dis, &r.a, &r.b, &r.step);

        // 将记录添加到对应学生的记录列表
        int student_index = h[idx]; // 获取学生在数组中的索引
        recVector[student_index].push_back(r);
    }

    // 处理每个学生的阳光长跑记录,统计有效次数
    for (int i = 1; i <= n; i++) {
        // 若该学生有阳光长跑记录,则检查每条记录的有效性
        if (!recVector[i].empty()) {
            long long last_end = 0; // 记录上一次有效跑步的结束时间(初始为0)

            // 遍历该学生的所有跑步记录
            for (int j = 0; j < recVector[i].size(); j++) {
                Record r = recVector[i][j];
                // 将距离从公里转换为米(原单位为公里,乘以1000)
                r.dis *= 1000;

                // 检查记录是否有效,若有效则阳光长跑次数+1
                if (check(r.date, r.Sa, r.Sb, r.Sc, r.Ea, r.Eb, r.Ec,
                          r.a, r.b, r.step, r.dis, stu[i].sex, last_end)) {
                    stu[i].SunshineTimes++;
                }
            }
        }
    }

    // 计算每个学生的总分数和等级,并输出结果
    for (int i = 1; i <= n; i++) {
        // 计算各项成绩
        int LongDisScore = LongDisScoreJudge(stu[i].sex, stu[i].LongDisData); // 长跑测试成绩
        int SunshineScore = SunshineScoreJudge(stu[i].SunshineTimes);         // 阳光长跑成绩
        // 专项计划出勤分(阳光长跑次数+训练营次数)
        int ChuQinScore = ChuQinScoreJudge(stu[i].SunshineTimes, stu[i].ExerciseTimes);

        // 总分数 = 专项成绩 + 长跑成绩 + 阳光长跑成绩 + 体质测试成绩 + 专项计划期末分 + 出勤分
        int score = stu[i].PEScore + LongDisScore + SunshineScore +
                    stu[i].HealthScore + stu[i].SpecialScore + ChuQinScore;

        // 根据总分数获取等级
        string grade = GradeJudge(score);

        // 输出学号、总分数、等级
        printf("%d %d ", stu[i].idx, score);
        cout << grade << '\n';
    }
    
    return 0;
}
posted @ 2025-08-19 22:31  九三青梧  阅读(29)  评论(0)    收藏  举报