題目大意:求公元前 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加上我跳過的這些年中的平年數量,這個可以簡單前綴和相減得到。剩下的小於366天的部分暴力一天一天跳即可。
這樣做細節相對少一點,而且處理儒略歷和格里高利歷的方法類似,可以直接開兩個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年的都跳掉,然后剩下的天數直接通過映射表查詢它會對應到那個日期。
代碼:
#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;
}