题解 P7075 【儒略日】
囧仙
2020-11-07 22:37:33
## $\stackrel{\text{废话}}{\textbf{前言}}$
- 这条题目成功加冕成为新一代毒瘤题我是没想到的。但 $\frak{u1s1}$ ,个人感觉这题不是很难,主要考的选手的细心程度。
- 个人总结,只要**不贪**,这题还是挺容易做的。
## 题解
让我们首先解决一些题面中给出的特殊情况。
- 起始时间为公元前 $4713$ 年 $1$ 月 $1$ 日。
- 公元 $1582$ 年 $10$ 月 $4$ 日以前,当一个年份被四整除时为闰年。但是如果一个年份在公元前,则应当将这个年份减去 $1$ 后进行闰年判定。
- 公元 $1582$ 年 $10$ 月 $5$ 日至 $10$ 月 $14$ 日被删除。
- 公元 $1582$ 年 $10$ 月 $15$ 日以后,一个年份为闰年,当且仅当它被四百整除或者能被四整除但不能被一百整除。
- 公元前一年的下一年,就是公元元年。
让我们先把闰年判定写出来。为了区分公元前后,我们用数字的正负进行区分。
```cpp
bool chk(i64 y){
if(y<=1582ll){
if(y<0ll) y=-y-1ll; return y%4ll==0ll;
}
return (y%400ll==0ll)||(y%4ll==0ll&&y%100ll!=0ll);
}
```
题目给了相当多的信息,尤其是非常多的时段分类……但是,如果你仔细思考时段的长度,你能发现,
$$\textbf{4713.1.1 BC 至 1582.10.15 一共不超过 2.5}\boldsymbol{\times 10^6 }\textbf{天}$$
所以,我们能非常简单的写出这样一份代码,直接枚举这所有天的情况。
```cpp
up(1,2305447,i){
Y[i]=Y[i-1],M[i]=M[i-1],D[i]=D[i-1]+1;
if(M[i]==12ll&&D[i]==32ll){
if(Y[i]==-1) Y[i]=1; else ++Y[i];
M[i]=1,D[i]=1;
} else if(Y[i]==1582ll&&M[i]==10ll&&D[i]==5ll){
Y[i]=1582ll,M[i]=10ll,D[i]=15ll;
} else if(M[i]==2ll&&chk(Y[i])&&D[i]==29ll){
continue;
} else if(D[i]>MD[M[i]]) ++M[i],D[i]=1;
}
//其中Yi,Mi,Di分别为第i天的年份、月份、日数。
//chk(Yi) 是检查Yi年是否是闰年。
```
- 经过一些暴力计算,我们能发现,$1600.1.1$ 的儒略日是 $2305448$ 。
好了,我们已经砍掉这题一半的难度了。下面考虑 $1600$ 年之后的事情。
要注意的是,**从天数推日期总是比从日期推天数难**。
这里启发我们二分年份,计算它和 $1600.1.1$ 的日期之差。(为什么是差值?因为差值可以忽略掉一些可能导致的错误,因为两个错误相减后会互相消去,可以大大提升调对的概率)。月份和日期都可以直接枚举。这题就做完了。
```cpp
//计算绝对日期
#define f(x) ((x)/4-(x)/100+(x)/400)
i64 clc(i64 y,i64 m,i64 d){
return 365ll*(y-1ll)+f(y-1ll)+((chk(y)&&m>=3ll)?1ll:0ll)+S[m-1ll]+d;
//这部分并不需要非常严格,因为我们只需要计算日期和1600.1.1的差值。
}
//主程序片段
dn(qread(),1,Q){
i64 t=qread(); if(t<=2305447ll){
i64 y=Y[t],m=M[t],d=D[t]; //直接从表中获取答案
if(y<0) printf("%lld %lld %lld BC\n",d,m,-y); //公元前
else printf("%lld %lld %lld\n",d,m, y); //公元后
} else {
t-=2305448ll; i64 y=0,m=1,d=1;
dn(30,0,i){ //二分
if(clc(y|(1<<i),m,d)-clc(1600,1,1)<=t) y|=1<<i;
}
up(1,12,i) if(clc(y,i,d)-clc(1600,1,1)<=t) m=i; //枚举月
up(1,31,i) if(clc(y,m,i)-clc(1600,1,1)<=t) d=i; //枚举日
printf("%lld %lld %lld\n",d,m,y);
}
}
```
## 参考代码
```cpp
#include<bits/stdc++.h>
#define up(l,r,i) for(int i=l,END##i=r;i<=END##i;++i)
#define dn(r,l,i) for(int i=r,END##i=l;i>=END##i;--i)
using namespace std;
typedef unsigned int u32;
typedef long long i64;
typedef unsigned long long u64;
i64 qread(){
i64 r=0,w=1,c=0;
for(c=getchar();!isdigit(c);c=getchar()) w=(c=='-'?-1:1); r=c-'0';
for(c=getchar(); isdigit(c);c=getchar()) r=r*10+c-'0';
return r*w;
}
const int MD[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
const int MAXN =2400000+3;
int Y[MAXN],M[MAXN],D[MAXN],S[13];
bool chk(i64 y){
if(y<=1582ll){
if(y<0ll) y=-y-1ll; return y%4ll==0ll;
}
return (y%400ll==0ll)||(y%4ll==0ll&&y%100ll!=0ll);
}
#define f(x) ((x)/4-(x)/100+(x)/400)
i64 clc(i64 y,i64 m,i64 d){
return 365ll*(y-1ll)+f(y-1ll)+((chk(y)&&m>=3ll)?1ll:0ll)+S[m-1ll]+d;
}
int main(){
freopen("julian.in","r",stdin);
freopen("julian.out","w",stdout);
Y[0]=-4713ll,M[0]=1ll,D[0]=1ll; up(1,12,i) S[i]=S[i-1]+MD[i];
up(1,2305447,i){
Y[i]=Y[i-1],M[i]=M[i-1],D[i]=D[i-1]+1;
if(M[i]==12ll&&D[i]==32ll){
if(Y[i]==-1) Y[i]=1; else ++Y[i];
M[i]=1,D[i]=1;
} else if(Y[i]==1582ll&&M[i]==10ll&&D[i]==5ll){
Y[i]=1582ll,M[i]=10ll,D[i]=15ll;
} else if(M[i]==2ll&&chk(Y[i])&&D[i]==29ll){
continue;
} else if(D[i]>MD[M[i]]) ++M[i],D[i]=1;
}
dn(qread(),1,Q){
i64 t=qread(); if(t<=2305447ll){
i64 y=Y[t],m=M[t],d=D[t];
if(y<0) printf("%lld %lld %lld BC\n",d,m,-y);
else printf("%lld %lld %lld\n",d,m, y);
} else {
t-=2305448ll; i64 y=0,m=1,d=1;
dn(30,0,i){
if(clc(y|(1<<i),m,d)-clc(1600,1,1)<=t) y|=1<<i;
}
up(1,12,i) if(clc(y,i,d)-clc(1600,1,1)<=t) m=i;
up(1,31,i) if(clc(y,m,i)-clc(1600,1,1)<=t) d=i;
printf("%lld %lld %lld\n",d,m,y);
}
}
return 0;
}
//这是赛时代码,马蜂可能比较丑,请见谅。
```