P7075儒略日
Mr_HY43205 · · 个人记录
P7075 [CSP-S2020] 儒略日
题意
有
儒略日:从公元前
公历日期计算:
- 公元
1582 年10 月15 日及以后:格里高利历,即现行历法(当年份是400 的倍数,或年份是4 的倍数但不是100 的倍数时,该年为闰年)。 - 公元
1582 年10 月5 日至1582 年10 月14 日:不存在。 - 公元
1582 年10 月4 日及以前:儒略历(当年份为4 的倍数时,该年为闰年)。 - 公元零年不存在,公元前
1 年的下一年是公元1 年。
数据范围:
| 测试点编号 | ||
|---|---|---|
关键词:模拟,二分
解题思路
10分:Q=1000,r_i\leq365
询问天数不超过
时间复杂度为
40分:Q=10000,r_i\leq3\times10^5
询问天数的范围不止一年了,此时需要考虑闰年和平年天数不同的问题。由于
时间复杂度为
80分:Q=10^5,r_i\leq10^7
此时要考虑的情况就复杂了。
首先,在枚举方法上,我们可以采用按年枚举的方法。和按月枚举的思路基本相同,也是判断目前的天数是否大于这一年的天数。如果是,说明日期不在这一年内,减去这一年的天数并继续。否则,就在这一年里进行按月枚举。
值得注意的是,有几个问题需要解决:
首先,公元前后的年份枚举形式不同,会导致实现不方便。我们可以在枚举年份的时候,将公元前的年份对应到负数,并用公元前
其次,对于闰年的判断,涉及到两个不同的历法。于是,我们需要首先判断年份是否小于
还有一个问题在于
时间复杂度为
100分:Q=10^5,\text{答案年份不超过}\ 10^9
由于年份达到
按照刚才公元前取负的记录方法,年份的范围为
其天数由几个部分组成:年数,闰年数以及是否在公元
年数的部分,直接将年数差乘上
计算出儒略日后,我们就可以对年份进行二分,并在二分得出的年份内进行按月枚举。注意在输出时要考虑公元前后的输出格式。同时要注意,答案年份达到
时间复杂度为
本题完整代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
int Q;
ll ri;
int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
ll cal(ll year) { //对给定的年数,计算1月1日对应的儒略日
ll cnt = (year + 4712 + 3) / 4;
if (year >= 1600)
cnt += (year - 1600 + 399) / 400 - (year - 1600 + 99) / 100;
ll res = (year + 4712) * 365 + cnt;
if (year > 1582) res -= 10;
return res;
}
void count_date(ll day, ll year) { //在年内进行按月枚举
ll month;
bool is_leap = false;
if (year < 1600 && year % 4 == 0)
is_leap = true;
else if (year >= 1600 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)))
is_leap = true;
if (is_leap) days[2]++;
if (year == 1582) days[10] -= 10;
for (month = 1; month <= 12; month++) {
if (day > days[month]) day -= days[month];
else break;
}
if (year == 1582 && month == 10 && day >= 5) day += 10;
if (year <= 0) cout << day << " " << month << " " << -year + 1 << " BC" << endl;
else cout << day << " " << month << " " << year << endl;
if (is_leap) days[2]--;
if (year == 1582) days[10] += 10;
}
int main() {
freopen("julian.in","r",stdin);
freopen("julian.out","w",stdout);
cin >> Q;
while (Q--) {
cin >> ri;
ll l = -4712, r = 1000000001, res = 0;
while (l < r - 1) { //对年份进行二分,范围为[l,r)左闭右开区间
ll m = (l + r) >> 1;
res = cal(m);
if (res <= ri) l = m;
else r = m;
}
res = cal(l);
count_date(ri - res + 1, l);
}
return 0;
}