节日

顺推每年元旦的星期,利用月初星期和模 7 公式定位第几个指定星期。

OJ: shumeng

题目 ID: CSP201503C

难度:入门

标签:模拟日期

日期: 2026-07-31 16:21

形式化题目

已知 185018501111 日是星期二。对 y1y2y_1 \sim y_2 中的每一年,求该年 aa 月的第 bb 个星期 cc 是哪一天,以 yyyy/mm/dd 格式输出;若该年 aa 月不存在第 bb 个星期 cc,输出 none。星期编号 171 \sim 7 分别表示星期一 \sim 星期日。

思路

先看一个完全按题意逐日推进的基准程序,它从 18501850 年 1 月 1 日逐天数日历,遇到目标星期就计数:

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-07-31 16:21
 * update_at: 2026-08-17 22:51
 */
// brute.cpp:逐日模拟从 1850 年开始的日历。
#include <bits/stdc++.h>
using namespace std;

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

int days_in_month(int year, int month) {
    int days[13] = {0, 31, 28, 31, 30, 31, 30,
                    31, 31, 30, 31, 30, 31};
    if (month == 2 && is_leap(year)) return 29;
    return days[month];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int target_month, kth, target_weekday, first_year, last_year;
    cin >> target_month >> kth >> target_weekday >> first_year >> last_year;

    vector<int> answer(last_year - first_year + 1, 0);
    int weekday = 2;
    for (int year = 1850; year <= last_year; year++) {
        int seen = 0;
        for (int month = 1; month <= 12; month++) {
            int limit = days_in_month(year, month);
            for (int day = 1; day <= limit; day++) {
                if (month == target_month && weekday == target_weekday) {
                    seen++;
                    if (seen == kth && year >= first_year) {
                        answer[year - first_year] = day;
                    }
                }
                weekday = weekday % 7 + 1;
            }
        }
    }

    for (int year = first_year; year <= last_year; year++) {
        int day = answer[year - first_year];
        if (day == 0) {
            cout << "none\n";
        } else {
            cout << year << '/' << setw(2) << setfill('0') << target_month << '/'
                 << setw(2) << day << setfill(' ') << '\n';
        }
    }

    return 0;
}

brute.cpp 逐日模拟最忠实于题意,适合作为对拍基准,但最多要推进到 20502050 年,逐日做法只是够用。正式做法把“逐日”升级为“逐月、逐公式”计算。

核心公式

若目标月的 1 日是星期 first,则:

  • 该月第一个星期 c 的日期是 1+(cfirst+7)mod71 + (c - first + 7) \bmod 7
  • bb 个星期 c 的日期是第一个的日期再加 7(b1)7(b-1)
  • 若算出的日期超过该月天数,说明该月没有第 bb 个星期 c,输出 none

推算每月 1 日的星期

已知 18501850 年 1 月 1 日是星期二(编号 2)。对每一年的 1 月 1 日,先累加该年 1 月到目标月前一月的总天数,就能得到目标月 1 日的星期;处理完一年后再用当年的总天数(闰年 366、平年 365)推进到下一年元旦的星期。

代码

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-07-31 16:21
 * update_at: 2026-08-17 22:51
 */
#include <bits/stdc++.h>
using namespace std;

// 判断 year 是否为闰年:400 的倍数,或 4 的倍数但不是 100 的倍数。
bool is_leap(int year) {
    return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
}

// 返回 year 年 month 月的天数,2 月需要根据闰年特殊处理。
int days_in_month(int year, int month) {
    int days[13] = {0, 31, 28, 31, 30, 31, 30,
                    31, 31, 30, 31, 30, 31};
    if (month == 2 && is_leap(year)) return 29;
    return days[month];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int month, kth, weekday_needed, first_year, last_year;
    cin >> month >> kth >> weekday_needed >> first_year >> last_year;

    // 1 表示星期一,1850 年 1 月 1 日是星期二。
    // first_weekday_of_year 记录当前年份 1 月 1 日是星期几。
    int first_weekday_of_year = 2;
    for (int year = 1850; year <= last_year; year++) {
        // 从元旦星期顺推目标月 1 日是星期几。
        int first_weekday_of_month = first_weekday_of_year;
        for (int current_month = 1; current_month < month; current_month++) {
            first_weekday_of_month += days_in_month(year, current_month);
            first_weekday_of_month = (first_weekday_of_month - 1) % 7 + 1;
        }

        if (year >= first_year) {
            // 目标月 1 日是 first 时,第一个星期 c 的日期是 1+(c-first+7)%7。
            int first_day = 1 + (weekday_needed - first_weekday_of_month + 7) % 7;
            int answer_day = first_day + 7 * (kth - 1);
            if (answer_day > days_in_month(year, month)) {
                cout << "none\n";
            } else {
                cout << year << '/' << setw(2) << setfill('0') << month << '/'
                     << setw(2) << answer_day << setfill(' ') << '\n';
            }
        }

        // 用当年天数推进到下一年的元旦星期。
        first_weekday_of_year += is_leap(year) ? 366 : 365;
        first_weekday_of_year = (first_weekday_of_year - 1) % 7 + 1;
    }

    return 0;
}

复杂度

  • 时间:对每个年份只累加 a1a-1 个月,设年份数为 YY,总时间为 O(Y)O(Y)
  • 空间:只使用常量空间,O(1)O(1)

总结

日期题先固定星期的编号方式,再用“月初星期 + 模 7 偏移”公式定位目标日期。闰年规则只影响每月天数和下一年元旦的星期,把它封装成 is_leapdays_in_month 两个函数即可避免重复判断。