一道名題-(csp 儒略日)的心得與技巧

CraneWilliams發表於2023-04-09

引:

如果你見到一個oi對著 47131582 146097 2299160 顛顛地笑,不用懷疑,他是在做那道名題--《csp-s2020 T1 儒略日》

這道題,我做了三年,平均每年做一次,我來講講我的心得。

讀題

題面很長,細節很多,我們需要耐心細心的讀,此時多花一點時間是劃得來的。

我們得出大致關係如下

\[曆法\begin{cases} \begin{aligned} 公曆(日常用歷)\\ \\ \\ 儒略曆 \begin{cases} 公元前\\公元后\\\end{cases} \end{aligned} \end{cases} \]

我們對公曆都有一定的瞭解吧,比如 平年十二個月的天數, 比如小學教的 “一三五七八十臘”,四年一閏百年不閏四百年又閏,這些寶貴的經驗將成為我們解題的關鍵說的呢。

格里高利相較於公曆其實更為簡單,因為太簡單不準所以才改的嘛 ,就是閏年的計算規則不同,是每四年一閏。

解題

首先,這是一道人盡皆知的模擬題。模擬的概念太籠統了,實現起來也八仙過海,我來講講我覺得最適合入手這道關於日期的大模擬。

解題的方向

我們要想著化繁為簡,一個勁的分類討論不見得總是好事(我第一次就這麼G的)。

多想想在 coding , 寫程式碼的時間總是小於調 bug 的時間的。

我的思路

我們發現,將日期分為儒略曆和公曆是比較好的,將公元前的日期歸化到儒略曆的一部分,不用特殊處理,具體來講,將公元前的日期年份 \(y\) \(->\) \(\ -y + 1\) 比如BC 4713 變為 -4712 ,這陽處理閏年也方便。

我們來算一下儒略曆一共多少天

首先公元前天數 365 * 4713 + 4713 / 4

公元后的天數1581 * 365 + 1581 / 4 + 277 1582(不含)年以前的和1582年的277天。

總共 2299160 天。

對於較簡單的儒略曆,我們可以直接算

int y = -4712, m = 1, d = 1;
    if (x <= Ru) {
        y += x / (_1 * 4 + 1) * 4;
        x %= (_1 * 4 + 1);
        while(x >= (_1 + (y % 4 == 0))) x -= (_1 + (y % 4 == 0)), y++;
        while(x >= (M[m] + (m == 2 && y % 4 == 0))) x -= (M[m] + (m == 2 && y % 4 == 0)), m++;
        d += x;
        if (y < 1) {
            cout << d << ' ' << m << ' ' << 1 - y << ' ' << "BC" << endl;
        } else {
            cout << d << ' ' << m <<' ' << y << endl;
        }
    }

程式碼中(M[m] + (m == 2 && y % 4 ==0) 是處理閏年的二月日期(28->29)

注意的是, 1 1 4713 BC 是第0天

對於週期的儲存,我們可以這樣//公曆下

int _1 = 365, _4 = 4 * _1 + 1, _100 = 25 * _4 - 1, _400 = _100 * 4 + 1; //100,400是公曆下的
int M[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

對於公曆, 我們考慮將1582 10 15 到 1982 10 14日這400年打一個小表,這樣極大簡化了我們的計算量

else {
		x -= Ru;
		y = 0;
		y += x / _400 * 400; x %= _400;
		cout << a[x].d << ' ' << a[x].m << ' ' << y + a[x].y << endl;  
	}

其中a是打出來的表,a[x]是四百年中的第幾天,10月15 日算第一天。

那麼,怎麼打出這個表?

答:用手打 可以用結構體。

struct dt{
	int y, m, d;
	dt(){}
	dt(int _y, int _m, int _d) {
		y = _y, m = _m, d = _d;
	}
}a[maxn], be = {1582, 10, 15};

然後四百年迭代一遍

 dt v = be;
  for (int i = 1; i <= _400; i++) {
  	a[i] = v;
  	nxt(v);
  }

我們只需處理簡單的一天的日期跳轉

void nxt(dt &x) {
	x.d++;
	if (x.d > (M[x.m] + ck(x))) {
		x.d -= (M[x.m] + ck(x));
		x.m++;
	}
	if (x.m > 12) {
		x.m = 1;
		x.y++;
	}
}

ck 是處理閏年二月

bool ck(dt x) {
	if (x.y % 4 == 0 && x.y % 100 != 0 || x.y % 400 == 0) {
		return x.m == 2;
	} else return 0;
}

於是,我們便做完了這道大模擬。

完結撒花!

沒有完!還有您對作者文章的肯定/否定沒有留下,可以點贊或留言,作者都會看到!

$Our\ stories\ are\ still\ going\ on.\ $

相關文章