儒略历解题报告

儒略历解题报告

儒略日

从上次 csp 考场上下来之后,就一直对这题很有心理阴影。

然后想着今天补一补这个题,那么一些经验就是,不要太害怕他,不要总想着如何快速推一些东西,静下心来一点点看,然后多定义一些打表的数组,这个数据万一用到了呢?多定义一些简单函数,可以极大简化代码内部的细节特判,全部扔到函数里面判断。

首先我们考虑把格里高利历和儒略历划分开来,因为这个分界线是非常的明显的,而且分界线前后计算闰年的方式不同。

这个时候不要对自己的手算过于自信,当然,这个手算很容易,但是作为一个信竞生,也许我们应该更好的利用手边的工具,况且,前期的一些暴力打表和小函数,对于这类大模拟是很有用的。

那么我们首先把一些基础的定义出来,比如说如何判断闰年:

inline int gr(int x) {
    if (!(x % 400)) return 1;
    if ((x % 100) && !(x % 4)) return 1;
    return 0;
}

inline int rr(int x) {
    if (!(x % 4)) return 1;
    return 0;
}

中英混合命名,第一个是 \(g\) 格里高利 \(r\) (run) 年,第二个同理。

然后再把月份有多少天定义出来:

for (int i = 1; i <= 12; ++i) {
    if (i == 2) d[i] = 28;
    else if (i == 1 || i == 3 || i == 5 || i == 7 || i == 8 || i == 10 || i == 12) d[i] = 31;
    else d[i] = 30;
}

这个时候我们就可以很快的求出儒略历到底多少天了:

int sd = 0;
for (int i = -4712; i <= 1581; ++i) {
    sd += rr(i) ? 366 : 365;
}
for (int i = 1; i <= 9; ++i) {
    sd += d[i];
}

sd += 3;

暴力枚举整年并处理,暴力枚举整月并处理,最后处理多余的天,之所以这里 \(+=3\),是因为我们是按照 \(day\) 来判断的,而 \(day\) 表示的是往后走了多少天,所以这里不包括最后一天因为最后一天不能往后走了。

那么对于询问给出的 \(day\) 如果 if (day > 2299160) 就可以判断会到达格里高利历,否则不会。

好了,那么接下来我们知道会走到什么历了,那么对于某个历怎么计算呢?

注意到儒略历四年是一个周期,而格里高利历四百年是一个周期,于是我们可以打出一个钦定第一年为闰年的 400 年的表,具体来说,我们打出 \(ny[k], nm[k], nd[k]\) 表示从某个闰年 \(1\) 月 \(1\) 日开始走,会走到 400 年内的哪一年哪一月哪一日,这个处理也不难。

for (int i = 0, k = 0; i < 400; ++i) {
    for (int j = 1; j <= 12; ++j) {
        int dd = d[j];
        if (j == 2 && gr(i)) ++dd;
        for (int ds = 1; ds <= dd; ++ds) {
            ny[k] = i, nm[k] = j, nm[k] = ds;
            ++k;
        }
    }
}

枚举年月日,搞一个变量记录一下多少天了就行。

于是接下来就可以周期处理:

while (q--) {
    i64 day;
    std::cin >> day;
    int t = 0;
    if (day > 2299160) { // g 历
        day -= 2299161;
        day += 139810;
        // 手动修正到从 1200-1-1 开始走
        // 因为我们的表是要求从某个闰年开始,特殊的,格里高利钦定是必须为 400 倍数的某个闰年,毕竟你看那个打表方式就是钦定这是第一个 400 闰年,后面都不是
        t = 1200 + (day / 146097) * 400;
        // 从 1200 开始走,看有多少个 400 年

        day %= 146097; // 400 年 400 年的干掉
    }
    else { // r 历
        t = -4712 + (day / 1461) * 4; 
        // 4 年 4 年的干掉
        day %= 1461;
    }
    if (t + ny[day] > 0) { // 如果年份 + 剩余天数走的年份是 DC
        std::cout << nd[day] << ' ' << nm[day] << ' ' << t + ny[day] << '\n';
    }
    else { // 否则是 BC 捏
        std::cout << nd[day] << ' ' << nm[day] << ' ' << -(t + ny[day] - 1) << ' ' << "BC" << '\n';
    }
}

至于那个修正是怎么求的,第一步是先干到格里高利第一天,然后往前修正,这个往前修正需要修正多少天也好求:

int sd = 0;
for (int i = 1200; i <= 1581; ++i) 
    sd += gr(i) ? 366 : 365;
for (int i = 1; i < 10; ++i) 
    sd += d[i];
sd += 14;
std::cout << sd << '\n';

那么整体的代码如下:

#include <bits/stdc++.h>

using i64 = long long;
int d[13];

inline int gr(int x) {
    if (!(x % 400)) return 1;
    if ((x % 100) && !(x % 4)) return 1;
    return 0;
}

inline int rr(int x) {
    if (!(x % 4)) return 1;
    return 0;
}

int ny[146097], nm[146097], nd[146097];

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

    for (int i = 1; i <= 12; ++i) {
        if (i == 2) d[i] = 28;
        else if (i == 1 || i == 3 || i == 5 || i == 7 || i == 8 || i == 10 || i == 12) d[i] = 31;
        else d[i] = 30;
    }

    for (int i = 0, k = 0; i < 400; ++i) {
        for (int j = 1; j <= 12; ++j) {
            int dd = d[j];
            if (j == 2 && gr(i)) ++dd;
            for (int ds = 1; ds <= dd; ++ds) {
                ny[k] = i, nm[k] = j, nd[k] = ds;
                ++k;
            }
        }
    }

    int q;
    std::cin >> q;

    while (q--) {
        i64 day;
        std::cin >> day;
        int t = 0;
        if (day > 2299160) { // g
            day -= 2299161;
            day += 139810;
            t = 1200 + (day / 146097) * 400;

            day %= 146097;
        }
        else { // r
            t = -4712 + (day / 1461) * 4;
            day %= 1461;
        }
        if (t + ny[day] > 0) {
            std::cout << nd[day] << ' ' << nm[day] << ' ' << t + ny[day] << '\n';
        }
        else {
            std::cout << nd[day] << ' ' << nm[day] << ' ' << -(t + ny[day] - 1) << ' ' << "BC" << '\n';
        }
    }
}
上一篇:阿里云日志服务 ilogtail 卸载方法


下一篇:cpe