2020CSP-S T1 儒略历

儒略历

  传送门:儒略日


题目描述

 为了简便计算,天文学家们使用儒略日(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 年……以此类推的年份应视为闰年。

分析

  因为作者当时没有参加 CSP2020,所以幸运的没有在考场上被这道题恶心的经历 初赛没过,当时发烧了qwq。一看到这题的题目描述就知道这是一道 非常善良 的模拟题,然后就可以开始把流程先写下来。首先肯定是要分类讨论的,毕竟这玩意儿计算日期的方法都不一样(一个儒略历一个格里高利历)。所以我们首先先把日期分为两大类:

  1. 公园 1582 年 10 月 4 日之前
  2. 公园 1582 年 10 月 15 日之后

  我们一类一类讨论:

一、 公园 1582 年 10 月 4 日之前

  我们可以用计算器算一下,从公元前 4713 年 1 月 1日到公园 1583年 10 月 4 日总共有多少天。这个数字等于公元前的 1721424 天加上公元后的 577737 天,等于 2299161 天。因为这短时间内都是 4 年 1 闰,所以我们可以很容易算出来这个结果。
  而且我们可以发现,公元前 4713 年是一个闰年,所以为了计算方便,我们把总询问天数加上 1095 天,这样让起点变成了公元前 4716 年。这样处理之后,我们就可以利用 4 年 1 闰用 O(1) 处理出日期了。具体操作如下:

  1. 计算 “年份起点” y,我们先用总天数 n 除以(整除)4年(三平一润)的总天数( 4 × 365 + 1 = 1461 天 4 \times 365 + 1 = 1461 天 4×365+1=1461),就得到了总共经过了多少个 4 年。所以再用这个数字乘上 4 再减 4716就得到包含答案的四年的第一个年份了。要注意的是如果年份算出来大于等于 0,说明在公园后那么年份要加上1。
    y = { − 4716 + n 1461 × 4 ( y < 0 ) − 4716 + n 1461 × 4 + 1 ( y ≥ 0 ) y = \begin{cases} -4716 + \frac{n}{1461} \times 4(y<0)\\-4716 + \frac{n}{1461} \times 4 + 1(y \geq 0) \end{cases} y={4716+1461n×4(y<0)4716+1461n×4+1(y0)
  2. 计算 “剩余天数” n,我们用 n m o d    1461 n \mod 1461 nmod1461 就得到了在当前(也就是包含答案的那个四年)的 4 年中是第几天。
  3. 最后调整年月日,我们知道不管在格里高利历里还是儒略历里,月份的计算都是相同的,如果我们想根据上面我们算出的包含答案的四年的起点和从起点往后的天数计算出答案的年月日,我们唯一需要考虑的就是在这个 4 年中第 4 年是不是闰年。所以这个工作可以交给一个专门的函数来完成,而这个函数是在所有的时间范围上都适用的,然后就可以不管了。这个函数的实现我们放在最后再说。
二、公园 1582 年 10 月 15 日之后

  这里面就有一个很讨厌的地方就是 “历史上消失的十天”,大家应该都知道,这消失的十天就是为了补足前 1582 年使用儒略历带来的闰年的误差。又因为我们需要计算的方便,所以我们假设公元后都按照格里高利历来进行记年,也就是当年份是 400 的倍数,或日期年份是 4 的倍数但不是 100 的倍数时,该年为闰年。这样算就会多算十二天,所以我们在计算的时候再减去12天就好了,也就是说 n − 1721424 + 10 − 12 n - 1721424 + 10 - 12 n1721424+1012 就是公元后的总天数。
  然后我们就可以利用和上一中情况里面相同的做法,也就是找 400 年的周期的做法,来确定现在的日期,具体的操作如下:

  1. 计算 “年份起点” y,和上一中情况的做法基本类似,只不过这次的周期是 400 年。
    y = − 4716 + n 146097 × 400 y = -4716 + \frac{n}{146097} \times 400 y=4716+146097n×400
  2. 计算 “剩余天数” n,和上一中情况类似,计算 n = n m o d    146097 n = n \mod 146097 n=nmod146097 就好了。
  3. 讨论当前的 n 是否在前 300年里,从这里开始和上一种情况就有些不同了。
      1).首先这是第一种情况,不在前三百年里:在这种情况中,每个四年必定有一个闰年,所以这里就和处理儒略历的方法是一样的。具体处理办法如下:
      现将 y 加上300,然后让 n = n − 109572 n = n - 109572 n=n109572,得到最后 100 年的起点,然后再让 y 加上 n 1461 × 4 \frac{n}{1461} \times 4 1461n×4,这样把 y 的范围精确到了 4 年之内。然后让 n = n m o d    1461 n = n \mod 1461 n=nmod1461,算出剩余天数,最后用前文提到过的那个函数来调整年月日即可。
      2). 然后第二种情况,在前三百年里,首先把 “年份起点” 和 “剩余天数” 从 400 年的范围调整到每个一百年的范围:
    y + = n 36524 × 100 , n = n m o d    36524 y += \frac{n}{36524} \times 100,n = n \mod 36524 y+=36524n×100n=nmod36524
      然后再看是不是在每个 100 年 的最后 4 年里。如果在的话直接 y = y + 96 , n = n − 35064 y = y + 96,n = n - 35064 y=y+96n=n35064,然后再用那个函数调整年月日。如果不在,就和儒略历处理方法一样: y = y + n 1461 × 4 , n = n m o d    1461 y = y + \frac{n}{1461} \times 4, n = n \mod 1461 y=y+1461n×4n=nmod1461。最后用哪个函数调整年月日即可。
三、调整年月日那个函数

  这里我们来说上文中我们说过用来调整最后 4 年的年月日的函数如何实现。
  我们继续参照上文我们把年份归一到每个 4 年周期的办法,把年份先归一到每一年。具体做法如下:

  1. 如果 n > 1095,而且四年周期的最后一年是闰年,那就: y = y + 3 , n = n − 1095 y = y + 3,n = n - 1095 y=y+3n=n1095
  2. 如果不属于上面那种情况的话,那么要么这四年周期里都是平年,要么在前三年也都是闰年,所以 y = y + n 365 , n = n m o d    365 y = y + \frac{n}{365},n = n \mod 365 y=y+365nn=nmod365

  现在我们剩下的之后一个小于 365 的 n 了,这个 n 就是用来处理月份和日期的了。如果我们想继续用上面的思路,把月份归一到每个月的办法,那样需要讨论的情况就太多了,所以我们采用一种更加直接的办法:打表。我们把闰年中 366 天的月份和日期全都打出来,然后就可以直接从表格里用 n 取到答案的日期。需要特别注意的是,因为我们用的是闰年的表,我们只需要在上文的第二种情况处特判一下 n 是否大于 59,如果大于 59就把 n 再加一来跳过 2 月 29 号。


代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long

int q = 0; int n = 0;

void adjust(int n, int &y, int &m, int &d, bool is){
	static int toDate[366][2] = {
	{1, 1},  {1, 2},  {1, 3},  {1, 4},  {1, 5},  {1, 6},  {1, 7},  {1, 8},  {1, 9},  {1, 10},  {1, 11},  {1, 12},  {1, 13},  {1, 14},  {1, 15},  {1, 16},  {1, 17},  {1, 18},  {1, 19},  {1, 20},  {1, 21},  {1, 22},  {1, 23},  {1, 24},  {1, 25},  {1, 26},  {1, 27},  {1, 28},  {1, 29},  {1, 30},  {1, 31}, 
	{2, 1},  {2, 2},  {2, 3},  {2, 4},  {2, 5},  {2, 6},  {2, 7},  {2, 8},  {2, 9},  {2, 10},  {2, 11},  {2 ,12},  {2, 13},  {2, 14},  {2, 15},  {2, 16},  {2, 17},  {2, 18},  {2, 19},  {2, 20},  {2, 21},  {2, 22},  {2, 23},  {2, 24},  {2, 25},  {2, 26},  {2, 27},  {2, 28},  {2, 29}, 
	{3, 1},  {3, 2},  {3, 3},  {3, 4},  {3, 5},  {3, 6},  {3, 7},  {3, 8},  {3, 9},  {3, 10},  {3, 11},  {3, 12},  {3, 13},  {3, 14},  {3, 15},  {3, 16},  {3, 17},  {3, 18},  {3, 19},  {3, 20},  {3, 21},  {3, 22},  {3, 23},  {3, 24},  {3, 25},  {3, 26},  {3, 27},  {3, 28},  {3, 29},  {3, 30},  {3, 31}, 
	{4, 1},  {4, 2},  {4, 3},  {4, 4},  {4, 5},  {4, 6},  {4, 7},  {4, 8},  {4, 9},  {4, 10},  {4, 11},  {4, 12},  {4, 13},  {4, 14},  {4, 15},  {4, 16},  {4, 17},  {4, 18},  {4, 19},  {4, 20},  {4, 21},  {4, 22},  {4, 23},  {4, 24},  {4, 25},  {4, 26},  {4, 27},  {4, 28},  {4, 29},  {4, 30}, 
	{5, 1},  {5, 2},  {5, 3},  {5, 4},  {5, 5},  {5, 6},  {5, 7},  {5, 8},  {5, 9},  {5, 10},  {5, 11},  {5, 12},  {5, 13},  {5, 14},  {5, 15},  {5, 16},  {5, 17},  {5, 18},  {5, 19},  {5, 20},  {5, 21},  {5, 22},  {5, 23},  {5, 24},  {5, 25},  {5, 26},  {5, 27},  {5, 28},  {5, 29},  {5, 30},  {5, 31}, 
	{6, 1},  {6, 2},  {6, 3},  {6, 4},  {6, 5},  {6, 6},  {6, 7},  {6, 8},  {6, 9},  {6, 10},  {6, 11},  {6, 12},  {6, 13},  {6, 14},  {6, 15},  {6, 16},  {6, 17},  {6, 18},  {6, 19},  {6, 20},  {6, 21},  {6, 22},  {6, 23},  {6, 24},  {6, 25},  {6, 26},  {6, 27},  {6, 28},  {6, 29},  {6, 30}, 
	{7, 1},  {7, 2},  {7, 3},  {7, 4},  {7, 5},  {7, 6},  {7, 7},  {7, 8},  {7, 9},  {7, 10},  {7, 11},  {7, 12},  {7, 13},  {7, 14},  {7, 15},  {7, 16},  {7, 17},  {7, 18},  {7, 19},  {7, 20},  {7, 21},  {7, 22},  {7, 23},  {7, 24},  {7, 25},  {7, 26},  {7, 27},  {7, 28},  {7, 29},  {7, 30},  {7, 31}, 
	{8, 1},  {8, 2},  {8, 3},  {8, 4},  {8, 5},  {8, 6},  {8, 7},  {8, 8},  {8, 9},  {8, 10},  {8, 11},  {8, 12},  {8, 13},  {8, 14},  {8, 15},  {8, 16},  {8, 17},  {8, 18},  {8, 19},  {8, 20},  {8, 21},  {8, 22},  {8, 23},  {8, 24},  {8, 25},  {8, 26},  {8, 27},  {8, 28},  {8, 29},  {8, 30},  {8, 31}, 
	{9, 1},  {9, 2},  {9, 3},  {9, 4},  {9, 5},  {9, 6},  {9, 7},  {9, 8},  {9, 9},  {9, 10},  {9, 11},  {9, 12},  {9, 13},  {9, 14},  {9, 15},  {9, 16},  {9, 17},  {9, 18},  {9, 19},  {9, 20},  {9, 21},  {9, 22},  {9, 23},  {9, 24},  {9, 25},  {9, 26},  {9, 27},  {9, 28},  {9, 29},  {9, 30}, 
	{10, 1}, {10, 2}, {10, 3}, {10, 4}, {10, 5}, {10, 6}, {10, 7}, {10, 8}, {10, 9}, {10, 10}, {10, 11}, {10, 12}, {10, 13}, {10, 14}, {10, 15}, {10, 16}, {10, 17}, {10, 18}, {10, 19}, {10, 20}, {10, 21}, {10, 22}, {10, 23}, {10, 24}, {10, 25}, {10, 26}, {10, 27}, {10, 28}, {10, 29}, {10, 30}, {10, 31}, 
	{11, 1}, {11, 2}, {11, 3}, {11, 4}, {11, 5}, {11, 6}, {11, 7}, {11, 8}, {11, 9}, {11, 10}, {11, 11}, {11, 12}, {11, 13}, {11, 14}, {11, 15}, {11, 16}, {11, 17}, {11, 18}, {11, 19}, {11, 20}, {11, 21}, {11, 22}, {11, 23}, {11, 24}, {11, 25}, {11, 26}, {11, 27}, {11, 28}, {11, 29}, {11, 30}, 
	{12, 1}, {12, 2}, {12, 3}, {12, 4}, {12, 5}, {12, 6}, {12, 7}, {12, 8}, {12, 9}, {12, 10}, {12, 11}, {12, 12}, {12, 13}, {12, 14}, {12, 15}, {12, 16}, {12, 17}, {12, 18}, {12, 19}, {12, 20}, {12, 21}, {12, 22}, {12, 23}, {12, 24}, {12, 25}, {12, 26}, {12, 27}, {12, 28}, {12, 29}, {12, 30}, {12, 31}
	};
	if(n >= 1096 and is){
		y += 3; n -= 1095;
	}
	else{
		y += n / 365;
		n %= 365;
		if(n >= 59) n++;
	}
	m = toDate[n][0]; d = toDate[n][1];
}

void solve(int n, int &y, int &m, int &d){
	if(n < 2299161){
		n += 1095;
		y = -4716 + n / 1461 * 4;
		n %= 1461;
		adjust(n, y, m, d, true);
		if(y >= 0) y++;
	} 
	else{
		n -= 1721424; n += 10 - 12;
		y = 1 + n / 146097 * 400;
		n %= 146097;
		if(n >= 109572){
			y += 300; n -= 109572;
			y += n / 1461 * 4; n %= 1461;
			adjust(n, y, m, d, true);
		}
		else{
			y += n / 36524 * 100;
			n %= 36524;
			if(n >= 35064){
				y += 96; n -= 35064;
				adjust(n, y, m, d, false);
			}
			else{
				y += n / 1461 * 4;
				n %= 1461;
				adjust(n, y, m, d, true);
			}
		}
	}
}

signed main(){
	scanf("%lld", &q);
	while(q--){
		int y = 0, m = 0, d = 0;
		scanf("%lld", &n);
		solve(n, y, m, d);
		if(y < 0) 
			printf("%lld %lld %lld BC\n", d, m, -y);
		else 
			printf("%lld %lld %lld\n", d, m, y);
	}
	return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值