儒略日[CSP2020]


題面太長不放

https://www.luogu.com.cn/problem/P7075

題解

如何優雅地在開考40分鍾內完成此題?

首先最重要的一點:發現1600年之前的閏年規律都是每4年一次,而1600又正好是400的倍數,所以以1600作為分界線,分成1600年前后兩種情況比較好處理

其次,發現0年不存在也不方便,所以把所有的負數年份+1,輸出時再改回來

公元前4913年1月1日是第0天也不方便,所以把 r++

准備工作做好,現在來考慮怎么求解

(一)1600年及以前

如果 \(r<=M\),那么時間在1600年及以前(\(M\) 表示-4713到1600共有多少天)

發現-4713到1600只有6000多年,所以干脆把每一年的天數都算出來(然后求前綴和方便二分)

注意的點:

  1. 由於我們把所有的負數年份+1了,所以現在所有模4余0的年份都是閏年
  2. 1582年只有355天!!!

求出前綴和后(即-4713年到1600年及之前的某一年一共有多少天),就可以二分求出究竟是在哪一年的第幾天,然后跳轉到(三)

(二)1600年之后

如果 \(r>M\),那么時間在1600年之后,讓 \(r\) 減去 \(M\)

此時每400年為一個周期,400年一共經過了 \(366*97+365*303=146097\)

算出 \(p=\lfloor\dfrac{r}{146097}\rfloor\) 表示一共經過了多少個400年

\(q=r\bmod 146097\) 表示剩余多少天

注意!如果 \(q=0\),為方便后續計算,要讓 \(p\) 減去1,並將 \(q\) 設為 \(146097\)

之后就同(一)一樣,預處理出400年中每一年的天數和前綴和,就可以二分求出 \(q\) 究竟是在最后余下的不到400年中的哪一年,以及是這年的第幾天

如果二分求得是最后400年中的第 \(k\) 年,那么答案年份就應該是 \(1600+400p+k\)

然后跳轉到(三)

(三)一年內的判斷

現在我們已經確定了答案是哪一年,是這一年的第幾天,只需要找出月份和日期即可

發現 \(Q\le 10^5\),其實可以算出這一年的12個月各有多少天,然后從一月開始枚舉,就很容易算出是幾月幾日

注意前面給負數年份加了1,現在要減回來

注意事項:

  1. 1600年之前的閏年判定和1600年之后不同!注意區分
  2. 1582年的10月只有21天!
  3. 如果你得出的答案是1582年10月521日,給日期加上10!1582.10.51582.10.14不存在!

注意事項3成功導致我100pts -> 40pts /kk

寫多幾個函數一定會比全部擠在main函數中條理更清晰

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

template <typename T>
inline void read(T &num) {
	T x = 0, f = 1; char ch = getchar();
	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1;
	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
	num = x * f; 
}

ll ttt, M = 5000; //M作為數組的偏移量,防止數組越界 
ll n;
ll months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, nmonth[13];
ll Aday[10005], Bday[10005]; //A:1600之前; B:400年的周期 
ll days400 = 146097;

void initrun() {
	for (ll i = -4712; i <= 1600; i++) Aday[i+M] = 365;
	for (ll i = -4712; i <= 1600; i += 4) {
		Aday[i+M] = 366;
	}
	Aday[1582+M] = 355; //!!!
	for (ll i = -4712; i <= 1600; i++) Aday[i+M] += Aday[i+M-1];
	
	for (ll i = 1; i <= 400; i++) {
		if ((i % 400 == 0) || (i % 4 == 0 && i % 100 != 0)) Bday[i] = Bday[i-1] + 366;
		else Bday[i] = Bday[i-1] + 365; 
	} 
}

void printans(ll y, ll m, ll d) {
	if (y < 0) printf("%lld %lld %lld BC\n", d, m, -y);
	else printf("%lld %lld %lld\n", d, m, y);
}

void inyear(ll y, ll d) { //y年的第d天 
	for (ll i = 1; i <= 12; i++) nmonth[i] = months[i];
	if (y == 1582) { //in 1582 
		nmonth[10] = 21;
	} else if (y <= 1600) { //before 1600
		if (y % 4 == 0) nmonth[2] = 29;
	} else { //after 1600
		if ((y % 400 == 0) || (y % 4 == 0 && y % 100 != 0)) { 
			nmonth[2] = 29;
		}
	}
	if (y <= 0) y--; //恢復負數年份 
	for (ll i = 1; i <= 12; i++) {
		if (d <= nmonth[i]) {
			if (y == 1582 && i == 10 && d >= 5) d += 10; //100pts->40pts
			printans(y, i, d);
			break;
		} else {
			d -= nmonth[i];
		}
	}
}

void solve1(ll x) { //before 1600
	ll l = -4712, r = 1600, mid = 0, ans = 0;
	while (l <= r) {
		mid = (l + r) >> 1;
		if (Aday[mid+M] >= x) {
			ans = mid;
			r = mid - 1;
		} else l = mid + 1;
	}
	inyear(ans, x - Aday[ans+M-1]);
}

void solve2(ll x) { //after 1600
	ll p = x / days400;
	ll q = x % days400;
	if (!q) {
		p--; q = days400;
	}
	ll l = 1, r = 400, mid = 0, ans = 0;
	while (l <= r) {
		mid = (l + r) >> 1;
		if (Bday[mid] >= q) {
			ans = mid;
			r = mid - 1;
		} else l = mid + 1;
	}
	inyear(1600 + p * 400 + ans, q - Bday[ans-1]);
}

int main() {
	read(ttt);
	initrun();
	while (ttt--) {
		read(n); n++; 
		if (n <= Aday[1600+M]) { //before 1600
			solve1(n);
		} else { //after 1600
			solve2(n - Aday[1600+M]);
		} 
	}	
	return 0;
} 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM