1. 程式人生 > 其它 >P7075 [CSP-S2020] 儒略日

P7075 [CSP-S2020] 儒略日

目錄
P7075 [CSP-S2020] 儒略日

題目

題目描述

  1. 為了簡便計算,天文學家們使用儒略日(Julian day)來表達時間。所謂儒略日,其定義為從公元前 4713 年 1 月 1 日正午 12 點到此後某一時刻間所經過的天數,不滿一天者用小數表達。若利用這一天文學歷法,則每一個時刻都將被均勻的對映到數軸上,從而得以很方便的計算它們的差值。

    現在,給定一個不含小數部分的儒略日,請你幫忙計算出該儒略日(一定是某一天的中午 12 點)所對應的公曆日期。

    我們現行的公曆為格里高利曆(Gregorian calendar),它是在公元 1582 年由教皇格里高利十三世在原有的儒略曆(Julian calendar)的基礎上修改得到的(注:儒略曆與儒略日並無直接關係)。具體而言,現行的公曆日期按照以下規則計算:

    1. 公元 1582 年 10 月 15 日(含)以後:適用格里高利曆,每年一月 \(31\) 天、 二月 \(28\) 天或 \(29\) 天、三月 \(31\) 天、四月 \(30\) 天、五月 \(31\) 天、六月 \(30\) 天、七月 \(31\) 天、八月 \(31\) 天、九月 \(30\) 天、十月 \(31\) 天、十一月 \(30\) 天、十二月 \(31\) 天。其中,閏年的二月為 \(29\) 天,平年為 \(28\) 天。當年份是 \(400\) 的倍數,或日期年份是 \(4\) 的倍數但不是 \(100\) 的倍數時,該年為閏年。
    2. 公元 1582 年 10 月 5 日(含)至 10 月 14 日(含):不存在,這些日期被刪除,該年 10 月 4 日之後為 10 月 15 日。
    3. 公元 1582 年 10 月 4 日(含)以前:適用儒略曆,每月天數與格里高利曆相同,但只要年份是 \(4\) 的倍數就是閏年。
    4. 儘管儒略曆於公元前 45 年才開始實行,且初期經過若干次調整,但今天人類習慣於按照儒略曆最終的規則反推一切 1582 年 10 月 4 日之前的時間。注意,公元零年並不存在,即公元前 1 年的下一年是公元 1 年。因此公元前 1 年、前 5 年、前 9 年、前 13 年……以此類推的年份應視為閏年。

輸入格式

第一行一個整數 \(Q\),表示詢問的組數。
接下來 \(Q\) 行,每行一個非負整數 \(r_i\),表示一個儒略日。

輸出格式

  1. 對於每一個儒略日 \(r_i\)

    ,輸出一行表示日期的字串 \(s_i\)。共計 \(Q\) 行。 \(s_i\) 的格式如下:

    1. 若年份為公元后,輸出格式為 Day Month Year。其中日(Day)、月(Month)、年(Year)均不含前導零,中間用一個空格隔開。例如:公元
      2020 年 11 月 7 日正午 12 點,輸出為 7 11 2020
    2. 若年份為公元前,輸出格式為 Day Month Year BC。其中年(Year)輸出該年份的數值,其餘與公元后相同。例如:公元前 841 年 2 月 1 日正午 12
      點,輸出為 1 2 841 BC

輸入輸出樣例

輸入 #1

3
10
100
1000

輸出 #1

11 1 4713 BC
10 4 4713 BC
27 9 4711 BC

輸入 #2

3
2000000
3000000
4000000

輸出 #2

14 9 763
15 8 3501
12 7 6239

輸入 #3

見附件中的 julian/julian3.in

輸出 #3

見附件中的 julian/julian3.ans

說明/提示

【資料範圍】

測試點編號 \(Q =\) \(r_i \le\)
\(1\) \(1000\) \(365\)
\(2\) \(1000\) \(10^4\)
\(3\) \(1000\) \(10^5\)
\(4\) \(10000\) \(3\times 10^5\)
\(5\) \(10000\) \(2.5\times 10^6\)
\(6\) \(10^5\) \(2.5\times 10^6\)
\(7\) \(10^5\) \(5\times 10^6\)
\(8\) \(10^5\) \(10^7\)
\(9\) \(10^5\) \(10^9\)
\(10\) \(10^5\) 年份答案不超過 \(10^9\)

思路 & 程式碼

80pts

考試的時候看題目太繁瑣,就直接跳過了(雖然也耽誤了半個小時).後來發現80pts還是比較容易的.少了80pts,但是避免了被這題炸裂心態.

其實看80pts時\(r_i\le 10^7\).不難想到按\(r\)排序,直接一天一天模擬.

對於一個日期,我們只要求出它的下一天時什麼時候就好.(我的做法的核心就只在next這一個函式)

struct date {
	int y , d , m;
};
inline date next(date d) {
	if(d.y == 1582 && d.m == 10 && d.d == 4){
		d.d = 15;
		return d;
	}
	
	bool run;
	if(d.y < 0)
		run = ((-d.y - 1) % 4 == 0);
	else if(d.y <= 1582)
		run = (d.y % 4 == 0);
	else
		run = (d.y % 4 == 0 && d.y % 100 != 0 || d.y % 400 == 0);
		
	++d.d;
	if(d.d > mon[run][d.m])
		++d.m , d.d = 1;
	if(d.m > 12)
		++d.y , d.m = 1;
	if(d.y == 0)
		d.y = 1;
	return d;
}

程式碼

#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
//#pragma GCC optimized(2)
using namespace std;
const int Q = 100010;

int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = c - '0' + (re << 1) + (re << 3) , c = getchar();
	return negt ? -re : re;
}

const int mon[2][15] = {
	{0,31,28,31,30,31,30,31,31,30,31,30,31},
	{0,31,29,31,30,31,30,31,31,30,31,30,31}
};
struct date {
	int y , d , m;//year date month. y<0表示公元前
};
inline date next(date d) {
	if(d.y == 1582 && d.m == 10 && d.d == 4){
		d.d = 15;
		return d;
	}
	
	bool run;
	if(d.y < 0)
		run = ((-d.y - 1) % 4 == 0);
	else if(d.y <= 1582)
		run = (d.y % 4 == 0);
	else
		run = (d.y % 4 == 0 && d.y % 100 != 0 || d.y % 400 == 0);
		
	++d.d;
	if(d.d > mon[run][d.m])
		++d.m , d.d = 1;
	if(d.m > 12)
		++d.y , d.m = 1;
	if(d.y == 0)
		d.y = 1;
	return d;
}

struct query {
	int r , id;
} q[Q];
bool cmp(query a , query b) {
	return a.r < b.r;
}
int qnum;
date ans[Q];
signed main() {
	qnum = read();
	for(int i = 1 ; i <= qnum ; i++)
		q[i].r = read() , q[i].id = i;

	sort(q + 1 , q + qnum + 1 , cmp);

	int cnt = 0;
	date d;
	d.y = -4713 , d.m = 1 , d.d = 1;
	for(int i = 1 ; i <= qnum ; i++) {
		while(cnt < q[i].r)
			++cnt , d = next(d);
		ans[q[i].id] = d;
	}

	for(int i = 1 ; i <= qnum ; i++) {
		if(ans[i].y < 0) {
			printf("%d %d %d BC\n" , ans[i].d , ans[i].m , -ans[i].y);
		} else {
			printf("%d %d %d\n" , ans[i].d , ans[i].m , ans[i].y);
		}
	}
	return 0;
}

100pts

考慮到1600年之後奇奇怪怪的規定就會變少,所以我們用2000.1.1作為基線,前面直接暴力,後面做出一些優化.

根據閏年的定義,我們以400年為一個週期(400年的天數總是固定的),算出400年中有97個閏年,即400年有\(365 \times 400 + 97\)天(具體留給電腦算就好).

我們從時間基線開始,用\(400\times 2^j\)​做倍增(應該也可以\(O(1)\)​做,但是沒必要,本人太弱沒搞出來).讓當前年數與目標年數之差不超過400,然後一年一年地和目標日期逼近,使日期只差不超過365天,最後按一天一天逼近即可.計算次數分別為\(\log r,400,360\),乘上\(q\),是在\(10^8\)​以內的,可以通過.

程式碼

#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
//#pragma GCC optimized(2)
using namespace std;
const int Q = 100010;
const int DayOf400Y = 365 * 400 + 97;
const int LogR = 30;

int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = c - '0' + (re << 1) + (re << 3) , c = getchar();
	return negt ? -re : re;
}

const int mon[2][15] = {
	{0,31,28,31,30,31,30,31,31,30,31,30,31},
	{0,31,29,31,30,31,30,31,31,30,31,30,31}
};
struct date {
	int y , d , m;
};
inline date next(date d) {
	if(d.y == 1582 && d.m == 10 && d.d == 4){
		d.d = 15;
		return d;
	}
	
	bool run;
	if(d.y < 0)
		run = ((-d.y - 1) % 4 == 0);
	else if(d.y <= 1582)
		run = (d.y % 4 == 0);
	else
		run = (d.y % 4 == 0 && d.y % 100 != 0 || d.y % 400 == 0);
		
	++d.d;
	if(d.d > mon[run][d.m])
		++d.m , d.d = 1;
	if(d.m > 12)
		++d.y , d.m = 1;
	if(d.y == 0)
		d.y = 1;
	return d;
}

struct query {
	int r , id;
} q[Q];
bool cmp(query a , query b) {
	return a.r < b.r;
}
int qnum;
date ans[Q];
signed main() {
	qnum = read();
	for(int i = 1 ; i <= qnum ; i++)
		q[i].r = read() , q[i].id = i;

	sort(q + 1 , q + qnum + 1 , cmp);

	int cnt = 0;
	date d;
	d.y = -4713 , d.m = 1 , d.d = 1;
	
	int i;
	for(i = 1 ; d.y < 2000 && i <= qnum ;) {
		while(cnt < q[i].r && d.y < 2000)
			++cnt , d = next(d);
		if(d.y < 2000)
			ans[q[i].id] = d , ++i;
	}
	//此時d=2020年1月1日,cnt為這天的儒略日
	int cnt_;//當前儒略日
	date d1;//當前格里高利曆日期
	for( ; i <= qnum ; i++) {
		cnt_ = cnt , d1 = d;//從時間基線開始
		
		for(int j = LogR ; j >= 0 ; j--)//400年*2^j 倍增
			if(cnt_ + (DayOf400Y << j) <= q[i].r)
				cnt_ += (DayOf400Y << j) , d1.y += (400ll << j);
		
		while(cnt_ + 366 <= q[i].r) {//年份逼近
			if(d1.y % 4 == 0 && d1.y % 100 != 0 || d1.y % 400 == 0)
				cnt_ += 366;
			else
				cnt_ += 365;
			d1.y += 1;
		}
		while(cnt_ < q[i].r)//日期逼近
			++cnt_ , d1 = next(d1);
		ans[q[i].id] = d1;
	}

	for(int i = 1 ; i <= qnum ; i++) {
		if(ans[i].y < 0) {
			printf("%d %d %d BC\n" , ans[i].d , ans[i].m , -ans[i].y);
		} else {
			printf("%d %d %d\n" , ans[i].d , ans[i].m , ans[i].y);
		}
	}
	return 0;
}