• 题解 儒略日


    题解 儒略日

    题目链接

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

    题目分析

    (Tle 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
    ",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
    ",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
    ",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
    ",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 imes 365+0)
    -4713+1 (1 imes 365+1)
    -4713+2 (2 imes 365+1)
    -4713+3 (3 imes 365+1)
    -4713+4 (4 imes 365+1)
    -4713+5 (5 imes 365+2)
    (dots) (dots)

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

    [Y imes 365+lfloorfrac{Y+3}{4} floor ]

    于是就可以通过二分来求出 (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 imes 365+0)
    1+1 (1 imes 365+0)
    1+2 (2 imes 365+0)
    1+3 (3 imes 365+0)
    1+4 (4 imes 365+1)
    1+5 (5 imes 365+1)
    (dots) (dots)

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

    [Y imes 365+lfloorfrac{Y}{4} floor ]

    于是就可以通过二分来求出 (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 的倍数才是闰年”这个限制条件,由于 (4mid (1583+1),4mid (1+3)) ,此时需要经过的天数是:

    [Y imes 365+lfloorfrac{Y+2}{4} floor ]

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

    [Y imes 365+lfloorfrac{Y+2}{4} floor-lfloorfrac{Y+82}{100} floor ]

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

    [Y imes 365+lfloorfrac{Y+2}{4} floor-lfloorfrac{Y+82}{100} floor+lfloorfrac{Y+382}{400} floor ]

    于是就可以通过二分来求出 (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
    ",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
    ",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
    ",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
    ",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('
    ');
    	}
    }
    int main(){
    //	getsum();
    	work();
    	return 0;
    }
    
  • 相关阅读:
    Scanner扫描器
    数据类型的转换
    万年历java
    冒泡排序、选择排序(升序排序,降序排序)
    数组
    字符串大小写转换(全转)(大小写互转)
    数据库两表联查、多表联查,多重联查
    js监测输入框字数(一个汉字等于两个字母),超出限制输入无效
    几条经验让jQuery用的更漂亮
    JS实现URL的拼接转换
  • 原文地址:https://www.cnblogs.com/lsq147/p/13943630.html
Copyright © 2020-2023  润新知