题解 儒略日

题解 儒略日

题目链接

这种题目无论出到那场比赛都会被喷的惨的,这说明出题人早已做好了心理准备,大家快喷他。

题目分析

\(T\le 10^5\) 显然出题人是想放一个 \(\log_2\) 过的,所以我们算年份的时候可以使用二分。

大力分类讨论,我考场上分了这些时间段:

  1. 公元前 4713 年 1 月 1 日(含) 至 公元前 1 年 12 月 31 日(含)
  2. 公元 1 年 1 月 1 日(含) 至 公元 1582 年 10 月 4 日(含)
  3. 公元 1582 年 10 月 15 日(含) 至 公元 1582 年 12 月 31 日(含)
  4. 公元 1583 年 1 月 1 日(含) 至 公元 1000000000 年 12 月 31 日(含)

第一步找到这些时间段之间的断点:

const int data0[13]={0,31,28,31,30,31,30,31,31,30,31,30,31},//平年
		  data1[13]={0,31,29,31,30,31,30,31,31,30,31,30,31};//闰年
void getyear(void){//这个是用来获取平年一年天数的,也就是 365 天
	int tmp=0;
	for(int i=1;i<=12;++i)
		tmp+=data0[i];
	printf("year:%d\n",tmp);
}
void getsum(void){
	int sum=0;
	for(int i=4713;i>=1;--i)
		sum+=365+(i%4==1);
	//reach 1.1.1
	printf("reach 1.1.1:%d\n",sum);
	for(int i=1;i<=1581;++i)
		sum+=365+(i%4==0);
	//reach 1582.1.1
	for(int i=1;i<=9;++i)
		sum+=data0[i];
	//reach 1582.10.1
	sum+=4;
	//reach 1582.10.5 1582.10.15
	printf("reach 1582.10.15:%d\n",sum);
	sum+=17;
	//reach 1582.11.1
	for(int i=11;i<=12;++i)
		sum+=data0[i];
	//reach 1583.1.1
	printf("reach 1583.1.1:%d\n",sum);
}

执行 getsum() 函数,可以得到如下结果:

reach 1.1.1:1721424
reach 1582.10.15:2299161
reach 1583.1.1:2299239

于是我们可以得到,四个时间段儒略日的取值范围分别是 \([0,1721424),[1721424,2299161),[2299161,2299239),[2299239,+\infty)\)

const int R0=1721424,R1=2299161,R2=2299239;

然后对每个时间段分别考虑。

公元前 4713 年 1 月 1 日(含) 至 公元前 1 年 12 月 31 日(含)

二分年份 \(Y\) ,考虑要从 公元前 4713 年 1 月 1 日 到 公元前 4713+\(Y\) 年 1 月 1 日 一共经过了多少天。

先列出表格表示每一年有多少天(不妨用负数表示公元前):

年份 天数
-4713+0 366
-4713+1 365
-4713+2 365
-4713+3 365
-4713+4 366
-4713+5 365
\(\dots\) \(\dots\)

求出到每一年 1 月 1 日需要的天数:

年份 天数
-4713+0 \(0\times 365+0\)
-4713+1 \(1\times 365+1\)
-4713+2 \(2\times 365+1\)
-4713+3 \(3\times 365+1\)
-4713+4 \(4\times 365+1\)
-4713+5 \(5\times 365+2\)
\(\dots\) \(\dots\)

可以得到,从 公元前 4713 年 1 月 1 日 到 公元前 4713+\(Y\) 年 1 月 1 日 经过的天数为:

\[Y\times 365+\lfloor\frac{Y+3}{4}\rfloor \]

于是就可以通过二分来求出 \(Y\) 了:

long long getyear0(long long r){
	// r 表示从公元前 4713 年 1 月 1 日开始还需要经过多少天
	long long L=0,R=4712;
	while(L<R){
		long long M=(L+R+1)>>1;
		long long D=(M+3)/4+M*365;
		if(D>r)R=M-1;else L=M;
	}
	return L;
}

得出 \(Y\) 之后,我们就可以直接枚举月份了,于是可以写出如下代码(需要注意一点,就是我代码中的 write() 表示输出, pc(x) 等同于 putchar(x) ):

if(r<R0){
	//before 1.1.1
	long long Y=getyear0(r);
	r-=(Y+3)/4+Y*365;++r;
	//这里 ++r 是因为此时已经是 1 月 1 日了,而后面 r 还需要用来计算天数,所以 r 初始就是 1
	Y=4713-Y;long long M=1;
	// Y 是年份, M 是月份
	if(Y%4!=1){//平年
		while(r>data0[M])
			r-=data0[M],++M;
	}
	else{//闰年
		while(r>data1[M])
			r-=data1[M],++M;
	}
	write(r),pc(' '),write(M),pc(' '),write(Y),pc(' ');
	pc('B'),pc('C');
}

公元 1 年 1 月 1 日(含) 至 公元 1582 年 10 月 4 日(含)

二分年份 \(Y\) ,考虑要从 公元 1 年 1 月 1 日 到 公元 1+\(Y\) 年 1 月 1 日 一共经过了多少天。

先列出表格表示每一年有多少天:

年份 天数
1+0 365
1+1 365
1+2 365
1+3 366
1+4 365
1+5 365
\(\dots\) \(\dots\)

求出到每一年 1 月 1 日需要的天数:

年份 天数
1+0 \(0\times 365+0\)
1+1 \(1\times 365+0\)
1+2 \(2\times 365+0\)
1+3 \(3\times 365+0\)
1+4 \(4\times 365+1\)
1+5 \(5\times 365+1\)
\(\dots\) \(\dots\)

可以得到,从 公元 1 年 1 月 1 日 到 公元 1+\(Y\) 年 1 月 1 日 经过的天数为:

\[Y\times 365+\lfloor\frac{Y}{4}\rfloor \]

于是就可以通过二分来求出 \(Y\) 了:

long long getyear1(long long r){
	// r 表示从公元 1 年 1 月 1 日开始还需要经过多少天
	long long L=0,R=1581;
	while(L<R){
		long long M=(L+R+1)>>1;
		long long D=M/4+M*365;
		if(D>r)R=M-1;else L=M;
	}
	return L;
}

得出 \(Y\) 之后,我们就可以直接枚举月份了,于是可以写出如下代码:

if(r>=R0&&r<R1){
	//before 1582.10.5 1582.10.15
	r-=R0;
	long long Y=getyear1(r);
	r-=Y/4+Y*365;++r;
	//这里 ++r 是因为此时已经是 1 月 1 日了,而后面 r 还需要用来计算天数,所以 r 初始就是 1
	Y=Y+1;long long M=1;
	// Y 是年份, M 是月份
	if(Y%4!=0){//平年
		while(r>data0[M])
			r-=data0[M],++M;
	}
	else{//闰年
		while(r>data1[M])
			r-=data1[M],++M;
	}
	write(r),pc(' '),write(M),pc(' '),write(Y);
}

公元 1582 年 10 月 15 日(含) 至 公元 1582 年 12 月 31 日(含)

10 月有 31 天,所以 10 月 15 日到 11 月 1 日需要经过 17 天,如果还需要经过的天数不足 17 天,就说明月份是 10 月,否则就枚举月份:

if(r>=R1&&r<R2){
	//before 1583.1.1
	r-=R1;
	if(r<17){
		r+=15;//从 10 月 15 日开始算起
		write(r),pc(' '),write(10),pc(' '),write(1582);
	}
	else{
		r-=17;++r;
		//这里 ++r 是因为此时已经是 1582 年 11 月 1 日了,而后面 r 还需要用来计算天数,所以 r 初始就是 1
		long long M=11;
		// M 是月份
		while(r>data0[M])
			r-=data0[M],++M;
		write(r),pc(' '),write(M),pc(' '),write(1582);
	}
}

公元 1583 年 1 月 1 日(含) 至 公元 1000000000 年 12 月 31 日(含)

二分年份 \(Y\) ,考虑要从 公元 1583 年 1 月 1 日 到 公元 1583+\(Y\) 年 1 月 1 日 一共经过了多少天。

这里计算的时候肯定就不能仅仅 \(\div 4\) 再向下取整了,因为如果一个年份是 100 的倍数还必须是 400 的倍数才是闰年。

首先不考虑“一个年份是 100 的倍数还必须是 400 的倍数才是闰年”这个限制条件,由于 \(4\mid (1583+1),4\mid (1+3)\) ,此时需要经过的天数是:

\[Y\times 365+\lfloor\frac{Y+2}{4}\rfloor \]

再考虑把所有年份是 100 的倍数的年份额外造成的贡献减掉,由于 \(100\mid (1583+17),100\mid (17+83)\) ,此时需要经过的天数是:

\[Y\times 365+\lfloor\frac{Y+2}{4}\rfloor-\lfloor\frac{Y+82}{100}\rfloor \]

再考虑把所有年份是 400 的倍数的年份造成的贡献加上,由于 \(400\mid (1583+17),400\mid (17+383)\) ,此时需要经过的天数就是:

\[Y\times 365+\lfloor\frac{Y+2}{4}\rfloor-\lfloor\frac{Y+82}{100}\rfloor+\lfloor\frac{Y+382}{400}\rfloor \]

于是就可以通过二分来求出 \(Y\) 了:

long long getyear2(long long r){
	// r 表示从公元 1583 年 1 月 1 日开始还需要经过多少天
	long long L=0,R=1000000000;
	while(L<R){
		long long M=(L+R+1)>>1;
		long long D=(M+2)/4-(M+82)/100+(M+382)/400+M*365;
		if(D>r)R=M-1;else L=M;
	}
	return L;
}

得出 \(Y\) 之后,我们就可以直接枚举月份了,于是可以写出如下代码:

if(r>=R2){
	r-=R2;
	long long Y=getyear2(r);
	r-=(Y+2)/4-(Y+82)/100+(Y+382)/400+Y*365;++r;
	//这里 ++r 是因为此时已经是 1583 年 1 月 1 日了,而后面 r 还需要用来计算天数,所以 r 初始就是 1
	Y+=1583;long long M=1;
	// Y 是年份, M 是月份
	if(Y%4!=0||(Y%100==0&&Y%400!=0)){
		while(r>data0[M])
			r-=data0[M],++M;
	}
	else{
		while(r>data1[M])
			r-=data1[M],++M;
	}
	write(r),pc(' '),write(M),pc(' '),write(Y);
}

参考代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ch() getchar()
#define pc(x) putchar(x)
using namespace std;
template<typename T>void read(T&x){
	static int f;static char c;
	for(c=ch(),f=1;c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c>='0'&&c<='9';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>void write(T x){
	static int q[64];int cnt=0;
	if(x==0)return pc('0'),void();
	if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10,x/=10;
	while(cnt--)pc(q[cnt]+'0');
}
const int data0[13]={0,31,28,31,30,31,30,31,31,30,31,30,31},//平年
		  data1[13]={0,31,29,31,30,31,30,31,31,30,31,30,31};//闰年
void getyear(void){//这个是用来获取平年一年天数的,也就是 365 天
	int tmp=0;
	for(int i=1;i<=12;++i)
		tmp+=data0[i];
	printf("year:%d\n",tmp);
}
void getsum(void){
	int sum=0;
	for(int i=4713;i>=1;--i)
		sum+=365+(i%4==1);
	//reach 1.1.1
	printf("reach 1.1.1:%d\n",sum);
	for(int i=1;i<=1581;++i)
		sum+=365+(i%4==0);
	//reach 1582.1.1
	for(int i=1;i<=9;++i)
		sum+=data0[i];
	//reach 1582.10.1
	sum+=4;
	//reach 1582.10.5 1582.10.15
	printf("reach 1582.10.15:%d\n",sum);
	sum+=17;
	//reach 1582.11.1
	for(int i=11;i<=12;++i)
		sum+=data0[i];
	//reach 1583.1.1
	printf("reach 1583.1.1:%d\n",sum);
}
const int R0=1721424,R1=2299161,R2=2299239;
long long getyear0(long long r){
	// r 表示从公元前 4713 年 1 月 1 日开始还需要经过多少天
	long long L=0,R=4712;
	while(L<R){
		long long M=(L+R+1)>>1;
		long long D=(M+3)/4+M*365;
		if(D>r)R=M-1;else L=M;
	}
	return L;
}
long long getyear1(long long r){
	// r 表示从公元 1 年 1 月 1 日开始还需要经过多少天
	long long L=0,R=1581;
	while(L<R){
		long long M=(L+R+1)>>1;
		long long D=M/4+M*365;
		if(D>r)R=M-1;else L=M;
	}
	return L;
}
long long getyear2(long long r){
	// r 表示从公元 1583 年 1 月 1 日开始还需要经过多少天
	long long L=0,R=1000000000;
	while(L<R){
		long long M=(L+R+1)>>1;
		long long D=(M+2)/4-(M+82)/100+(M+382)/400+M*365;
		if(D>r)R=M-1;else L=M;
	}
	return L;
}
void work(void){
	int q;read(q);
	while(q--){
		long long r;read(r);
		if(r<R0){
			//before 1.1.1
			long long Y=getyear0(r);
			r-=(Y+3)/4+Y*365;++r;
			//这里 ++r 是因为此时已经是 1 月 1 日了,而后面 r 还需要用来计算天数,所以 r 初始就是 1
			Y=4713-Y;long long M=1;
			// Y 是年份, M 是月份
			if(Y%4!=1){//平年
				while(r>data0[M])
					r-=data0[M],++M;
			}
			else{//闰年
				while(r>data1[M])
					r-=data1[M],++M;
			}
			write(r),pc(' '),write(M),pc(' '),write(Y),pc(' ');
			pc('B'),pc('C');
		}
		else if(r>=R0&&r<R1){
			//before 1582.10.5 1582.10.15
			r-=R0;
			long long Y=getyear1(r);
			r-=Y/4+Y*365;++r;
			//这里 ++r 是因为此时已经是 1 月 1 日了,而后面 r 还需要用来计算天数,所以 r 初始就是 1
			Y=Y+1;long long M=1;
			// Y 是年份, M 是月份
			if(Y%4!=0){//平年
				while(r>data0[M])
					r-=data0[M],++M;
			}
			else{//闰年
				while(r>data1[M])
					r-=data1[M],++M;
			}
			write(r),pc(' '),write(M),pc(' '),write(Y);
		}
		else if(r>=R1&&r<R2){
			//before 1583.1.1
			r-=R1;
			if(r<17){
				r+=15;//从 10 月 15 日开始算起
				write(r),pc(' '),write(10),pc(' '),write(1582);
			}
			else{
				r-=17;++r;
				//这里 ++r 是因为此时已经是 1582 年 11 月 1 日了,而后面 r 还需要用来计算天数,所以 r 初始就是 1
				long long M=11;
				// M 是月份
				while(r>data0[M])
					r-=data0[M],++M;
				write(r),pc(' '),write(M),pc(' '),write(1582);
			}
		}
		else if(r>=R2){
			r-=R2;
			long long Y=getyear2(r);
			r-=(Y+2)/4-(Y+82)/100+(Y+382)/400+Y*365;++r;
			//这里 ++r 是因为此时已经是 1583 年 1 月 1 日了,而后面 r 还需要用来计算天数,所以 r 初始就是 1
			Y+=1583;long long M=1;
			// Y 是年份, M 是月份
			if(Y%4!=0||(Y%100==0&&Y%400!=0)){
				while(r>data0[M])
					r-=data0[M],++M;
			}
			else{
				while(r>data1[M])
					r-=data1[M],++M;
			}
			write(r),pc(' '),write(M),pc(' '),write(Y);
		}
		pc('\n');
	}
}
int main(){
//	getsum();
	work();
	return 0;
}
posted @ 2020-11-08 10:49  xiaolilsq  阅读(374)  评论(0编辑  收藏  举报