[模拟] aw3761. 唯一最小数(日期问题+模板题)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[模拟] aw3761. 唯一最小数(日期问题+模板题)相关的知识,希望对你有一定的参考价值。
1. 题目来源
链接:3607. 打印日期
进阶:3573. 日期累加
进阶:[模拟] aw3489. 星期几(模拟+日期问题+蔡勒公式+模板题)
2. 题目解析
模拟题,有关于日期、时间、金钱等设计到单位进制的,模拟都可以写,但很难写出结构精巧的代码,一不小心就会写很多的 if-else
,且容易遗漏边界情况。
日期问题模板函数:
- 月份对应天数:
注意 2 月写成 28 天。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) { 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)
#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;
}
#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 也可。
在此月份也是直接跨,所以需要做归一化,避免许多边界情况。而第一种方法只跨年,一年内的天数转化为第一个问题直接进行枚举,故不必归一化。
以上是关于[模拟] aw3761. 唯一最小数(日期问题+模板题)的主要内容,如果未能解决你的问题,请参考以下文章