码迷,mamicode.com
首页 > 其他好文 > 详细

CSP2020 儒略日 题解

时间:2020-11-16 13:38:47      阅读:8      评论:0      收藏:0      [点我收藏+]

标签:printf   lang   处理   rest   题解   can   date   efi   前缀和   

题目大意:求公元前 4713 年 1 月 1 日 经过 r 天后的日期,公元 1582 年 10 月 4 日以前适用儒略历,公元 1582 年 10 月 15 日以后适用格里高利历
?q 次询问,\(q\leq 10^5\)

这题就我目前所知有三种做法:

做法一

大概就是先把儒略历和格里高利历的分界点判掉,然后两边分别先400年400年跳,100年100年跳,4年4年跳,1年1年跳,最后一个月一个月跳。
可想而知这种做法会有非常多的细节,很容易写挂,所以博主考场上并没有写这种做法。

做法二

也就是我考场上的做法。
一样的把分界点判掉,然后两边分开处理。
我先把每一年都看成是366天,这样可以直接往后跳 r / 366 年,然后令 r %= 366 ,然后因为我把一些平年看成了闰年,所以我需要把r加上我跳过的这些年中的平年数量,这个可以简单前缀和相减得到。
这样做细节相对少一点,而且处理儒略历和格里高利历的方法类似,可以直接开两个namespace然后复制粘贴过去。
然而因为蒟蒻考场上思路混乱这题还是写了1个多小时

考场代码:

#include<bits/stdc++.h>
using namespace std;
#define N 1007
#define M 1000007
#define LL long long
int Const=2299160; //r_i-=Const+1;
namespace Julian{
    int type[N]={0,1,-1,1,0,1,0,1,1,0,1,0,1};
    int base=-4716,year,month,day;

    void reset(){
	year=-4712,month=1,day=1;
    }
    int run(int x){
	return x/4;
    }
    int pingnian(int l,int r){
	l-=base,r-=base;
	int rn=run(r)-run(l-1);
	return (r-l+1)-rn;
    }
    inline void chkday(int x){
	if(day>x)day=1,month++;
    }
    void travel(int times)
    {
	while(times){
	    day++; times--;
	    if(month==2){
		if(year%4==0)chkday(29);
		else chkday(28);
	    }
	    else{
		if(type[month]==1)chkday(31);
		else chkday(30);
	    }
	    if(month==13)year++,month=1;
	}
    }
    void solve(LL times){
	reset();
	while(times>=366){
	    int dlt=times/366; times%=366;
	    int ping=pingnian(year,year+dlt-1);
	    year+=dlt,times+=ping;
	}
	travel(times);
	if(year>0)printf("%d %d %d\n",day,month,year);
	else printf("%d %d %d BC\n",day,month,-(year-1));
    }
}
namespace Gregorian
{
    int type[N]={0,1,-1,1,0,1,0,1,1,0,1,0,1};
    int base=1580,year,month,day;
    void reset(){
	year=1582,month=10,day=15;
    }
    int run(int x){
	return x/4-x/100+x/400;
    }
    int pingnian(int l,int r){
	//l-=base,r-=base;
	int rn=run(r)-run(l-1);
	return (r-l+1)-rn;
    }
    inline void chkday(int x){
	if(day>x)day=1,month++;
    }
    void travel(int times)
    {
	while(times){
	    day++; times--;
	    if(month==2){
		if(year%400==0||(year%4==0&&year%100!=0))chkday(29);
		else chkday(28);
	    }
	    else{
		if(type[month]==1)chkday(31);
		else chkday(30);
	    }
	    if(month==13)year++,month=1;
	}
    }
    void solve(LL times){
	reset();
	while(times>=366){
	    int dlt=times/366; times%=366;
	    int ping=pingnian(year+1,year+dlt);
	    year+=dlt,times+=ping;
	}
	travel(times);
	printf("%d %d %d\n",day,month,year);
    }
}

int main()
{
    LL r;
    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
	scanf("%lld",&r);
	if(r<=Const){
	    Julian::solve(r);
	}else{
	    Gregorian::solve(r-(Const+1));
	}
    }
    return 0;
}

做法三

这种做法更显得无脑暴力,因为它将每400年组成的循环节中每一天的日期全部打表打出来了。
首先注意到从公元前 4713 年 1 月 1 日 到公元 1582 年 10 月 4 日中间只有2299161天,我们可以把这2299161天的日期全部打表打出来,这样就可以直接回答所有儒略历的询问了。
然后处理格里高利历,我们建立两个映射,一个是从400年中的某个日期映射到它是这个400年中的第几天,另一个是从400年中的第几天映射到它在这400年中的日期,然后直接把整400年的都跳掉,然后剩下的天数直接通过映射表查询它会对应到那个日期,这样做是严格 \(O(q)\) 的,再带上一个大概 \(2e6\) 的预处理复杂度。

代码:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=3e6+7;
const int M=2e5+7;
struct date{
    int y,m,d;
}ju[N],gr[M];
int year,month,day;
int ty[13]={0,1,-1,1,2,1,2,1,1,2,1,2,1};
int tot,len,idx[407][13][33];
inline void chkday(int x){
    if(day>x)day=1,month++;
    if(month>12)month=1,year++;
}
void init(){
    year=-4712,month=1,day=1;
    ju[0]={year,month,day};
    while(year!=1582||month!=10||day!=4){
	day++; 
	if(ty[month]==1)chkday(31);
	else if(ty[month]==2)chkday(30);
	else {
	    if(year%4==0)chkday(29);
	    else chkday(28);
	}
	ju[++tot]={year,month,day};
    }
}

void init_2(){
    year=0,month=1,day=1;
    while(year!=400){
	idx[year][month][day]=len;
	gr[len++]={year,month,day};
	day++;
	if(ty[month]==1)chkday(31);
	else if(ty[month]==2)chkday(30);
	else{
	    if(year%400==0||(year%4==0&&year%100!=0))chkday(29);
	    else chkday(28);
	}
    }
}
void print(date v){
    if(v.y<=0){
	printf("%d %d %d BC\n",v.d,v.m,-(v.y-1));
    }
    else{
	printf("%d %d %d\n",v.d,v.m,v.y);
    }
}
void Gregor(LL times){
    year=1582,month=10,day=15;
    year+=(times/len)*400,times%=len;
    int rest=(year%400+400)%400;
    year-=rest;
    int ind=idx[rest][month][day];
    ind+=times;
    if(ind>=len)year+=400,ind-=len;
    year+=gr[ind].y,month=gr[ind].m,day=gr[ind].d;
    print({year,month,day});
}
int main()
{
    init(); init_2();
    int q;
    scanf("%d",&q);
    for(int i=1;i<=q;i++){
	LL r; scanf("%lld",&r);
	if(r<=tot)print(ju[r]);
	else Gregor(r-tot-1);
    }
    return 0;
}

CSP2020 儒略日 题解

标签:printf   lang   处理   rest   题解   can   date   efi   前缀和   

原文地址:https://www.cnblogs.com/lishuyu2003/p/13954520.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!