[模拟] aw3761. 唯一最小数(日期问题+模板题)

1. 题目来源

链接:3607. 打印日期

进阶:3573. 日期累加

进阶:[模拟] aw3489. 星期几(模拟+日期问题+蔡勒公式+模板题)

2. 题目解析

模拟题,有关于日期、时间、金钱等设计到单位进制的,模拟都可以写,但很难写出结构精巧的代码,一不小心就会写很多的 if-else,且容易遗漏边界情况。


日期问题模板函数:

  • 月份对应天数:
    const int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    
    注意 2 月写成 28 天。
  • 平闰年判断:
    int is_leap(int y) {
    	return y % 4 == 0 && y % 100 || y % 400 == 0;
    }
    
  • 得到某年某月有多少天:
    int get_days(int y, int m) {
    	if (m == 2) return months[m] + is_leap(y);
    	return months[m];
    }
    
  • 返回跳转一年经过的天数:y-m-d 跳转到 y+1-m-d 经过的天数。要确保来年该天仍存在,针对 2-29 这天需要特殊处理,转化为 3-1 日再进行操作。且平闰年需要判断,如果今年月份是 2 月份,平闰年算今年,否则平闰年算明年的。
    int get_year_days(int y, int m) {
    	if (m <= 2) return 365 + is_leap(y);
    	return 365 + is_leap(y + 1);
    }
    

问题类型:

  • 给定天数 s,起始日期 yy-mm-dd,确定 s 天后的具体日期,按 yyyy-mm-dd 输出。
    • s 小的时候,直接按天模拟即可,从起始位置一天天累加,直至累加天数到 s 即可。见 3607. 打印日期
    • s 大的时候,需要先跳过整年的天数,然后不足一年的再来按天数模拟即可。见 3573. 日期累加
  • 起始日期的星期,及终止日期。确定终止日期的星期。有蔡勒公式可以直接求,但是公式比较麻烦,问题等价于求两个日期中间相隔的天数,然后 %7 即可。见 [模拟] aw3489. 星期几(模拟+日期问题+蔡勒公式+模板题)

针对这一类问题,可以将日期转化为 1 月 1 日,做一个归一化,会避免判断很多边界情况。对 3573. 日期累加 做代码补充,使用彩铅大佬的代码!


时间复杂度: O ( n ) O(n) O(n)

空间复杂度: O ( 1 ) O(1) O(1)


3607. 打印日期

#include <bits/stdc++.h>

using namespace std;

int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int is_leap(int year) {
    return year % 4 == 0 && year % 100 || year % 400 == 0;
}

int get_days(int y, int m) {
    if (m == 2) return months[m] + is_leap(y);
    return months[m];
}

int main() {
    int year, s;
    while (cin >> year >> s) {
        int m = 1, d = 1;
        s -- ;
        while (s -- ) {
            if ( ++ d > get_days(year, m)) {
                m ++ ;
                d = 1;
                if (m > 12) {
                    year ++ ;
                    m = 1;
                }
            }
        }

        printf("%04d-%02d-%02d\n", year, m, d);
    }

    return 0;
}

3573. 日期累加

#include <bits/stdc++.h>

using namespace std;

const int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int is_leap(int y) {
    return y % 4 == 0 && y % 100 || y % 400 == 0;
}

int get_days(int y, int m) {
    return (m == 2 ? is_leap(y) : 0) + months[m];
}

// 细节。2 月前闰年算今年,2 月后,闰年算明年
int get_year_days(int y, int m) {
    if (m <= 2) return 365 + is_leap(y);
    return 365 + is_leap(y + 1);
}

int main() {
    int T; cin >> T; while (T -- ) {
        int y, m, d, a;
        cin >> y >> m >> d >> a;
        if (m == 2 && d == 29) a -- , m = 3, d = 1;
        
        while (a > get_year_days(y, m)) {
            a -= get_year_days(y, m);
            y ++ ;
        }

        while (a -- ) {
            if ( ++ d > get_days(y, m)) {
                d = 1;
                if ( ++ m > 12) {
                    y ++ ;
                    m = 1;
                }
            }
        }

        printf("%04d-%02d-%02d\n", y, m, d);
    }

    return 0;
}

使用归一化+前缀和思想

#include <iostream>

using namespace std;

int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int is_leap(int yy)
{
    return yy % 400 == 0 || yy % 4 == 0 && yy % 100;
}
void solve()
{
    int yy, mm, dd, diff;
    scanf("%d%d%d%d", &yy, &mm, &dd, &diff);

    //前缀和思想,合并到今年的第0天,归一化到今年的1月0日
    for (int i = 1; i < mm; ++ i)
    {
        diff += months[i];
        if (i == 2) diff += is_leap(yy);
    }
    diff += dd, mm = 1, dd = 0;		// mm=1 也可
    //先跨年
    while (diff > 365 + is_leap(yy)) diff -= 365 + is_leap(yy ++ );
    //再跨月。每个月天数跨,不足的就是该月中的具体日期
    while (mm != 2 && diff > months[mm] || mm == 2 && diff > months[mm] + is_leap(yy))
        diff -= (mm == 2 && is_leap(yy)) + months[mm], ++ mm;
    //结束
    printf("%04d-%02d-%02d\n", yy, mm, diff);
}
int main()
{
    int T = 1;
    scanf("%d", &T);
    while (T -- ) solve();
    return 0;
}

yy-mm-dd 转化为 yy-0-0 一年的天数加到 a 上。然后从 yy-0-0 先跨年,不足再跨月,最终剩余天数就是在该月的天数,不必处理。

注意代码细节,源代码在此是将 mm 置为 0 的,不过置为 1 也可。

在此月份也是直接跨,所以需要做归一化,避免许多边界情况。而第一种方法只跨年,一年内的天数转化为第一个问题直接进行枚举,故不必归一化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值