競技プログラミングにおける日付処理の典型パターン

競技プログラミングの過去問では、日付を扱う問題が頻出であり、特に省選レベルでは「時刻変換」「曖昧な日付の解釈」「回文日付の探索」の3つの典型的なパターンが見られます。これらは単なる文字列操作ではなく、日付の正当性検証・範囲制約・並び順保証といった実務的な要件を含むため、堅牢な実装が求められます。

ミリ秒から24時間表記への変換

入力として与えられたミリ秒単位の経過時間を、HH:MM:SS形式(24時間制、ゼロ埋め)に変換する問題です。1日は86,400,000ミリ秒(24×60×60×1000)であることに注意し、モジュロ演算で循環を処理します。

#include <iostream>
#include <cstdio>
using namespace std;

int main() {
    long long millis;
    cin >> millis;
    long long total_seconds = millis / 1000;
    long long hours = (total_seconds / 3600) % 24;
    long long minutes = (total_seconds / 60) % 60;
    long long seconds = total_seconds % 60;
    printf("%02ld:%02ld:%02ld", hours, minutes, seconds);
    return 0;
}

曖昧な日付表記の全候補列挙

「AA/BB/CC」形式の入力に対し、年号が2桁省略されているため、1960–2059年の範囲内で有効な日付候補をすべて生成し、昇順で出力します。3つの解釈パターン(YY/MM/DD、MM/DD/YY、DD/MM/YY)を網羅的に検証し、重複を排除してソート済み出力を得るには、単純な全探索が最も確実です。

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdio>
using namespace std;

const int days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

bool is_leap(int year) {
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

bool is_valid_date(int y, int m, int d) {
    if (y < 1960 || y > 2059 || m < 1 || m > 12) return false;
    if (d < 1) return false;
    if (m == 2 && is_leap(y)) {
        return d <= 29;
    }
    return d <= days_in_month[m];
}

int full_year(int yy) {
    return (yy >= 60) ? 1900 + yy : 2000 + yy;
}

int main() {
    int a, b, c;
    scanf("%d/%d/%d", &a, &b, &c);
    
    vector<int> candidates;
    // Pattern 1: YY/MM/DD → YYYY/MM/DD
    int y1 = full_year(a);
    if (is_valid_date(y1, b, c)) {
        candidates.push_back(y1 * 10000 + b * 100 + c);
    }
    // Pattern 2: MM/DD/YY → YYYY/MM/DD
    int y2 = full_year(c);
    if (is_valid_date(y2, a, b)) {
        candidates.push_back(y2 * 10000 + a * 100 + b);
    }
    // Pattern 3: DD/MM/YY → YYYY/MM/DD
    if (a != b && is_valid_date(y2, b, a)) {
        candidates.push_back(y2 * 10000 + b * 100 + a);
    }
    
    sort(candidates.begin(), candidates.end());
    auto last = unique(candidates.begin(), candidates.end());
    candidates.erase(last, candidates.end());
    
    for (int date : candidates) {
        int y = date / 10000;
        int m = (date / 100) % 100;
        int d = date % 100;
        printf("%d-%02d-%02d\n", y, m, d);
    }
    return 0;
}

次回の回文日付探索

与えられた日付(整数形式:YYYYMMDD)より後の最初の「回文日付」と「ABAB型回文日付」(例:20200202 → A=2,B=0)をそれぞれ探す問題です。回文判定は文字列変換でも可能ですが、整数演算による桁分割が高速かつ安全です。閏年対応を含む日付妥当性チェックを必須とし、探索範囲は自然に制限されます(21世紀内なら最大20991231まで)。

#include <iostream>
#include <cstdio>
using namespace std;

const int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

bool is_leap(int y) {
    return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
}

bool is_valid(int n) {
    int y = n / 10000;
    int m = (n / 100) % 100;
    int d = n % 100;
    if (m < 1 || m > 12) return false;
    if (d < 1) return false;
    if (m == 2 && is_leap(y)) return d <= 29;
    return d <= mdays[m];
}

bool is_palindrome(int n) {
    char s[9];
    sprintf(s, "%08d", n);
    for (int i = 0; i < 4; ++i) {
        if (s[i] != s[7-i]) return false;
    }
    return true;
}

bool is_abab_palindrome(int n) {
    char s[9];
    sprintf(s, "%08d", n);
    return s[0] == s[6] && s[1] == s[7] &&
           s[0] == s[2] && s[1] == s[3] &&
           s[0] != s[1];
}

int main() {
    int start;
    cin >> start;
    bool found_first = false;
    
    for (int candidate = start + 1; ; ++candidate) {
        if (!is_valid(candidate)) continue;
        
        if (!found_first && is_palindrome(candidate)) {
            printf("%d\n", candidate);
            found_first = true;
        }
        
        if (is_abab_palindrome(candidate)) {
            printf("%d\n", candidate);
            break;
        }
    }
    return 0;
}

タグ: C++ competitive-programming date-parsing leap-year formatting

5月17日 06:08 投稿