题解 P7075 【儒略日(洛谷民间数据)】

· · 题解

萌新第一次写题解,有写得不好的地方请多指教~~
今年CSP,两点半看题,整个人都傻了:第一题就是大模拟?!
读了十分钟才明白题意,其实模拟起来不是很复杂,只是坑点很多...

题意理解:

建立时间轴,以公元前4713年1月1日为原点,给定q个坐标,求坐标对应的公元时间。

易错点:

1.公元零年不存在。很重要,在判闰年时有用。
2.闰年的判断:有三种判断方式:

1)公元前1,5,9,...4的倍数加一为闰年。  
2)公元后且1582年前,4的倍数为闰年(没有公元零年!)。   
3)1582年后,4的倍数且不是100的倍数,或400的倍数为闰年。

3.1582.10.5-10.14不存在。
4.输入输出注意,某日某月某年,公元前加BC。

如果直接大模拟的话,大概是读入坐标,然后一段一段直接模拟。但这样不仅拖慢速度,且码量巨大让人秃头。而本题特征明显:公元前,公元后且1582年前和公元1582年后是明显的三段,因此可以在读入的过程中先将日期分在某个时间段内,有效减少了代码量。

int y=-4713,d=1,m=1;//年月日初始化
if(n>=1721424)y=1,n-=1721424;// 1721424是公元前的天数,判断年份属于哪一段

判断闰年部分:

    if(mon!=2)return 0; 
    if(year<0){//公元前的情况
        year=-year-1;//取正后-1
        if(year%4==0||year==0)return 1;
        return 0;
        }
    else{
        if(year<1582){//公元后,1582年前的情况
            if(year%4==0)return 1;
            return 0;
        }
        else{//1582年后       
            if((year%4==0&&year%100!=0)||year%400==0)return 1;
            return 0;
            }
        }
}   //这么写看着丑,而且麻烦,不过好在简单易懂

主函数:

int main(){
    cin>>q;
    while(q--){
        bool flag=0;
        scanf("%lld",&n);
        int y=-4713,d=1,m=1;
        if(n>=1721424)y=1,n-=1721424;
        while(n>0){
            if(d>month[m]+run(y,m))d=d-month[m]-run(y,m),m++;//日的进位
            if(m>12)m=1,y++;//月的进位
            if(n>=365+run(y,2)){//闰年则为366天
                n=n-365-run(y,2);
                y++;
                continue;//注意这里,当n大于一年就不会增加月份和日期
            }
            if(n>month[m]+run(y,m)){//判断是否是闰月
                n=n-month[m]-run(y,m);
                m++;
                continue;
            }
            else{
                d+=n;//小于一月,直接加在天数上
                n=0;
                break;
            }
        }
        if(y>1582)flag=1;
        if(y==1582&&m>10)flag=1;
        if(y==1582&&m==10&&d>4)flag=1;//若答案在1582.10.4之后,直接加上10天
        if(flag)d+=10;
        if(d>month[m]+run(y,m))d=d-month[m]-run(y,m),m++;//进位
        if(m>12)m=1,y++;
        if(y>0)printf("%d %d %d\n",d,m,y);//公元前还是公元后?输出
        else printf("%d %d %d BC\n",d,m,-y);
    }
}

优化:

当您看到这里的时候,这道题基本就做完了。
可我们看一下数据:q为1e5,答案年份不超过1e9...
不要慌,这代表我们需要一定的优化。
有两种优化方式:一种是读入所有的天数,离线排序后模拟。(因为太菜考场上没想到这个)
另一种是快速确认大概日期,再逐年模拟。
日期的确定和闰年有关。观察闰年的判定,我们发现:可以以400年为一个周期。
在1582年之前,每四年一个闰年,400年有146100天。
在1582年之后,400年有97个闰年,有146097天。
所以...

if(n>146097&&y>1582){
        y=y+n/146097*400;             //直接除加取模,跑得快~
        n%=146097;
    }
if(n>146100&&y+400<1582){   //保证四百年后也在1582年前
        y+=400;
        n-=146100;
    }

这样跑得也不会飞快,但起码最慢300ms也能过了。

总结:

总的来说,这是一道模拟好题,让我学到了很多。
重看本题,理解了意义不明的题面之后,思路也不复杂(模拟),代码并不会太长(66行)。
但数不胜数的坑点,特殊的数据和离奇的程序输出让许多OIer亲切的询问出题人住址(雾
在考场大概用了二十分钟读题分析,写代码用了四十分钟左右,对拍了二十分钟(?)
思路清晰的话还是好解决的~
收获:1.一定要认真读题,分析题面,找可能的坑点!
2.合理安排程序执行顺序,减少冗杂操作。
3先写暴力,再加适宜且不影响正确性的优化。
4.一定注意数据范围!(没开long long扣了10分QAQ)

谢谢朋友们,NOIp再见!