时间

GitHub跳转原题关系图返回列表

计算给定时间经过3小时30分后的时间,注意24小时制和跨日取模,需考虑特殊的冒号输入。

OJ: roj

题目 ID: 10012

难度:入门

标签:模拟字符串

创建: 2026-10-10 01:40

更新: 2026-10-10 01:40

形式化题目

给定一个 24 小时制的时间 h:mh:m(0≤h<240 \le h < 24,0≤m<600 \le m < 60),求该时间经过 3 小时 30 分钟后的时间。若超过或等于 24 小时,则跨日重置(即对一天的总时间取模)。输出格式同为 hh:mmhh:mm,不足两位补前导零。

正解

思路

本题最简便的做法是将时间统一转换为距当天 00:0000:00 的分钟数。

  1. 计算输入时间的总分钟数:h×60+mh \times 60 + m。
  2. 比赛时长为 3 小时 30 分钟,即 3×60+30=2103 \times 60 + 30 = 210 分钟。
  3. 将原时间的分钟数加上 210210 分钟后,因结果可能跨日,对一天总分钟数(24×60=144024 \times 60 = 1440)进行取模操作。
  4. 取模后的总分钟数转换回小时和分钟输出,即小时为 M/60M / 60,分钟为 M mod 60M \bmod 60,并格式化为两位数、不足补零。

代码

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-09 22:30
# update_at: 2026-10-10 00:05

import re
import sys

MATCH_MINUTES = 3 * 60 + 30   # 比赛时长 210 分钟
DAY_MINUTES = 24 * 60         # 一天的总分钟数 1440


def solve() -> None:
    """读入 hh:mm,输出加 3 小时 30 分钟后的时刻(跨日对一天 1440 分钟取模)。"""
    # 按「非数字字符」切分:题面样例用全角「:」,std.cpp 用半角 ':',两种都要能解析。
    parts = re.split(r'\D+', sys.stdin.read().strip())
    if len(parts) < 2:
        return
    hour, minute = map(int, parts[:2])
    total = (hour * 60 + minute + MATCH_MINUTES) % DAY_MINUTES
    print(f'{total // 60:02d}:{total % 60:02d}')


if __name__ == '__main__':
    solve()
cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-09 22:30
 * update_at: 2026-10-10 00:05
 */
// main.cpp:时间。
//   比赛时长固定 3 小时 30 分 = 210 分钟。把开始时刻换算成「距当天 00:00 的分钟数」,
//   加上 210 分钟后对一天的总分钟数 1440 取模(跨日回卷),再换算回 hh:mm 并补前导零。
//   ★ 冒号兼容:题面 PDF 的样例里冒号是全角「:」(U+FF1A,advance 1.0 em),
//     而官方参考实现 std.cpp 用的是半角 ':'(scanf("%d:%d"))。此处不区分冒号形式 ——
//     只认数字字符,分隔符一律跳过,故两种冒号下都与 std.cpp 语义一致。
#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

typedef long long ll;

const ll MATCH_MINUTES = 3 * 60 + 30;   // 比赛时长 210 分钟
const ll DAY_MINUTES = 24 * 60;         // 一天的总分钟数 1440

// 从一行里取出小时和分钟:连续的数字段就是这两个数,中间的分隔符直接跳过。
// 结果通过引用带出,避免为一对返回值引入额外的结构。
void read_time(ll &hour, ll &minute) {
    string line;
    getline(cin, line);
    bool in_hour = true;     // 当前数字归小时(false 表示归分钟)
    bool seen_digit = false; // 本段是否已读到数字,用来判断「分隔符是否已出现」
    for (ll i = 0, len = line.size(); i < len; i++) {
        char ch = line[i];
        if ('0' <= ch && ch <= '9') {
            if (in_hour) hour = hour * 10 + (ch - '0');
            else         minute = minute * 10 + (ch - '0');
            seen_digit = true;
        } else if (seen_digit && in_hour) {
            in_hour = false;    // 小时那段读完 ⇒ 后面的数字都归分钟
            seen_digit = false;
        }
    }
}

void solve() {
    ll hour = 0, minute = 0;
    read_time(hour, minute);
    ll total = (hour * 60 + minute + MATCH_MINUTES) % DAY_MINUTES;  // 跨日对 1440 取模
    cout << setfill('0') << setw(2) << total / 60 << ":"
         << setfill('0') << setw(2) << total % 60 << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    solve();
    return 0;
}

复杂度

  • 时间复杂度:O(1)\mathcal{O}(1)。只进行了常数次的算术运算。
  • 空间复杂度:O(1)\mathcal{O}(1)。仅使用少量的整型变量。

总结

  • 算法与复杂度:将时间换算成分钟后加上 210210,对 14401440 取模,然后再转回 hh:mmhh:mm,均为常数时间操作,时空复杂度均为 O(1)\mathcal{O}(1)。
  • 冒号之谜的三层独立证据:题面样例中出现了全角冒号 :。通过三层证据确认真实数据应为半角冒号 ::
    1. PDF CMap:ToUnicode 映射显示样例块用的是 <FF1A> 全角(0x6F21),格式行用的是 <003A> 半角(0x01E3),两者并非解析错误。
    2. 字体 Advance 宽度:样例块冒号宽 1.00 em,格式行宽 0.43 em,视觉与排版上严格区分。
    3. 参考代码与文件字节:std.cpp 使用 scanf("%d:%d")(字面量为半角),且内部非 ASCII 字节数为 0。可以判定样例块的全角冒号仅是 Word 导出 PDF 时的排版副产物。
  • 鲁棒的落盘实现策略:虽然官方 std.cpp 的实现采用窄解析(遇到全角会解析失败导致变量未初始化,如 23:31;空输入则产生未初始化 UB 如 14:43),但因题面样例使用了全角,落盘代码选择了“更宽容、按题面为主”的鲁棒策略。相比 std.cpp 的窄解析,落盘代码在 6 个对照点上具有以下差异:
    1. 合法半角(如 20:30):完全一致(1440组)。
    2. 全角冒号(如 20:30):std.cpp 解析失败,落盘按题面样例语义修正并解析正确。
    3. 空格分隔(如 00 30):std.cpp 解析错误,落盘兼容正确。
    4. 无分隔符(如 0000):std.cpp 解析错误,落盘兼容正确。
    5. 空输入:std.cpp 产生未初始化 UB,落盘行为确定。
    6. 多行输入:两者均只处理第一组数据,表现一致。
  • V1 变异体双面结果与验证:
    • 变体 D(只认半角冒号):在半角 1440 组测试中 0 失败(即未承重),但在全角 1440 组测试中触发 1437 失败。这凸显了“仅半角数据无法鉴别冒号处理漏洞”以及同源同错的风险点。
    • 其余变异:变体 A(加 3 小时没加 30 分钟)1440 组全失败;变体 B(去掉模 1440)暴露 210 组失败(20:30~23:59);变体 C(输出不补 0)暴露 740 组失败;变体 E(输出全角冒号)1440 组全失败。
  • 穷举验证与异源对比:测试覆盖了 h∈[0,23]×m∈[0,59]h \in [0, 23] \times m \in [0, 59] 共 1440 种时间的半角与全角输入(合计 2880 组数据)。落盘的 main.cpp 与 main.py 均实现 0 分歧。此外,引入两个完全异源的参考代码(ref_carry.cpp:分/时各自独立进位;ref_step.cpp:逐分钟推进走 210 步)也达到了 0 分歧,它们未走主解“距0点总分钟数取模”的推导路径,作为必要补充进一步确保了解法的严密性。
  • 证据强度声明:本题无本地数据(data/ 为空,属于静默空转,零证据),验证基准依赖官方 std.cpp。因其强度高于自写 ref,可作为可信锚点。但残留边界为其本身也被假设为正确(前述其处理全角/空输入有缺陷)。
  • V9 内存安全与 T38 环境限制:对 1440 组合法输入(含极值 00:0000:00 与 23:5923:59)进行 main.cpp 测试,UBSan stderr 全空,无 UB。对照组 control_ub.cpp 有效报警,探针有效。资源符合 config.json 限制(1000ms / 128MB)。
  • T9 规范验证:
    1. 极值处理:正确覆盖跨日边界输入如 23:5923:59 等。
    2. 数据类型:计算最大值为 1439+210=16491439 + 210 = 1649,在 int 范围内无溢出。
    3. 数组边界:未使用数组,无越界风险。
    4. 变量初始化:妥善处理读取与转换,规避了原生 scanf 解析失败带来的未初始化 UB 问题。
    5. 输入输出:对补零与换行等输出细节严格把关。