3 条题解
-
0
太棒了!自己动手编写数据生成器是算法进阶的必经之路。
对于这道题,需要特别向你说明一点:因为所有的正常解法时间复杂度都是 ,所以我们无法通过扩大数据范围来“卡”时间复杂度。这道题的测试数据设计的核心完全在于【逻辑分支的全面覆盖】与【边界陷阱(Corner Cases)】。
初学者做这道题最容易犯的错误就是把闰年规则写错(比如只判断了
% 4,漏掉了% 100的特判)。因此,我们的数据必须狠狠地揪出这种逻辑漏洞!下面为你精心设计的 10 个测试点覆盖策略以及完整的 C++ 数据生成器代码。
核心设计思路(10 个测试点分布)
- 测试点 1 (
2022 1):样例1。测试基础的大月(31天)。 - 测试点 2 (
2020 2):样例2。普通的闰年 2 月(能被4整除,不能被100整除),输出 29。 - 测试点 3 (
2023 4):测试基础的小月(30天)。 - 测试点 4 (
2100 2):【核心杀手数据】“百年不闰”陷阱!能被4整除但也能被100整除(且不能被400整除),这是平年,输出 28。用来卡掉只写了A % 4 == 0的错解。 - 测试点 5 (
2000 2):【核心杀手数据】“四百年再闰”边界!能被400整除,这是闰年,输出 29。用来卡掉写了A % 100 == 0就全部当成平年的错解。 - 测试点 6 (
2021 2):最普通的平年 2 月,输出 28。 - 测试点 7 (
2024 8):测试 8 月(容易出错的地方,因为 7 月和 8 月连续两个大月),输出 31。 - 测试点 8 (
3000 2):最大年份边界。3000 是百年不闰的平年,输出 28。 - 测试点 9 (
2996 2):接近最大边界的普通闰年,输出 29。 - 测试点 10 (
2500 12):普通大月测试,且包含 12 月边界。
数据生成器完整 C++ 代码 (
generator.cpp)请将以下代码保存为
generator.cpp,并在本地编译运行。它会在同级目录下瞬间生成1.in~10.in和1.out~10.out共 20 个文件。#include <iostream> #include <fstream> #include <string> #include <vector> using namespace std; // 标程函数:使用最优的“查表法”生成标准答案 int solve(int A, int B) { const int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 闰年特判 if (B == 2 && ((A % 4 == 0 && A % 100 != 0) || (A % 400 == 0))) { return 29; } // 非闰年2月或其他月份直接查表 return days[B]; } int main() { // 优化系统 IO 速度 ios::sync_with_stdio(false); cin.tie(nullptr); // 存储精选的 10 个测试用例,格式为 {年份A, 月份B} // 使用 struct 或者 pair 都可以,这里用简单的 struct 方便阅读 struct TestCase { int year; int month; }; vector<TestCase> test_cases = { {2022, 1}, // Test 1: 样例 1,普通大月 {2020, 2}, // Test 2: 样例 2,普通闰年 2 月 {2023, 4}, // Test 3: 普通小月 {2100, 2}, // Test 4: 杀手数据!百年不闰(平年,28天) {2000, 2}, // Test 5: 杀手数据!四百年再闰(闰年,29天),年份下限边界 {2021, 2}, // Test 6: 普通平年 2 月 {2024, 8}, // Test 7: 连续大月测试(8月是31天) {3000, 2}, // Test 8: 年份上限边界,百年不闰(平年,28天) {2996, 2}, // Test 9: 接近上限的普通闰年 {2500, 12} // Test 10: 年终月份边界(12月) }; // 循环生成 10 组测试数据 for (int i = 0; i < test_cases.size(); ++i) { int A = test_cases[i].year; int B = test_cases[i].month; // --- 1. 生成 .in 文件 --- string in_filename = to_string(i + 1) + ".in"; ofstream fout_in(in_filename); if (!fout_in.is_open()) { cerr << "无法创建文件: " << in_filename << "\n"; continue; } // 注意格式,用空格隔开,末尾换行 fout_in << A << " " << B << "\n"; fout_in.close(); // --- 2. 调用标程计算正确答案 --- int ans = solve(A, B); // --- 3. 生成 .out 文件 --- string out_filename = to_string(i + 1) + ".out"; ofstream fout_out(out_filename); if (!fout_out.is_open()) { cerr << "无法创建文件: " << out_filename << "\n"; continue; } fout_out << ans << "\n"; fout_out.close(); // 终端输出生成日志,方便核对 cout << "成功生成 Test " << (i + 1) << "\t" << "| 输入: " << A << " " << B << "\t" << "| 答案: " << ans << "\n"; } cout << "\n所有测试数据生成完毕!文件总大小不足 100 Bytes,OJ系统可极速读取。" << endl; return 0; }OJ 部署建议与总结
- 防爆栈/防除零/防超时:对于这道题目而言,因为无任何递归调用、没有除法(仅有取模
%)、时间复杂度绝对恒定,所以原生的测试数据生成是绝对安全的。 - 考察区分度:把这组数据放到 OJ 后,你会发现:
- 那些没有判断
% 100的同学,会在 Test 4 和 Test 8 拿到 Wrong Answer (WA)。 - 那些把整除条件写混淆,没有正确组合
&&和||优先级的同学,会在 Test 5 拿到 WA。 - 靠背诵“135781012”口诀但是把 8 月遗漏的同学,会在 Test 7 栽跟头。
- 那些没有判断
- 文件大小:单组数据输入输出极其精简(一行几个字符),所有的
.in和.out文件加在一起通常不到 100 字节,远小于 2MB,符合最高标准的轻量化测试需求。
- 测试点 1 (
-
0
你好!作为你的教练,我很高兴看到你准备好将刚才的思路转化为真正的代码了。
这道题非常特殊,因为它不存在循环,所以无论你怎么写,时间复杂度都是 级别的(只需要运行几步就结束了)。因此,我们这道题的演进过程,不是“算法时间维度的优化”,而是**“代码编写逻辑的优化”(从臃肿容易出错,演进到极简优雅)**。
下面为你展示三个版本的完整可运行代码,严格遵循 NOIP/CSP 的 C++14 标准。
版本一:新手直觉版 —— 穷举所有的
if-else刚刚接触编程的同学,最容易想到的就是把 1 到 12 月挨个判断一遍。这种写法逻辑最直白,但也最长。
#include <iostream> using namespace std; int main() { // 优化输入输出速度 ios::sync_with_stdio(false); cin.tie(nullptr); int A, B; // 易错点1:题目说先输入A(年),再输入B(月)。千万别写反了! cin >> A >> B; // 枚举所有的月份 if (B == 1) { cout << 31 << "\n"; } else if (B == 2) { // 关键点:2月份需要判断是否为闰年 // 闰年判断口诀:四年一闰且百年不闰,或四百年再闰 // 易错点2:注意逻辑运算符 && (且) 和 || (或) 的组合,最好用括号括起来明确优先级 if ((A % 4 == 0 && A % 100 != 0) || (A % 400 == 0)) { cout << 29 << "\n"; } else { cout << 28 << "\n"; } } else if (B == 3) { cout << 31 << "\n"; } else if (B == 4) { cout << 30 << "\n"; } else if (B == 5) { cout << 31 << "\n"; } else if (B == 6) { cout << 30 << "\n"; } else if (B == 7) { cout << 31 << "\n"; } else if (B == 8) { cout << 31 << "\n"; } else if (B == 9) { cout << 30 << "\n"; } else if (B == 10) { cout << 31 << "\n"; } else if (B == 11) { cout << 30 << "\n"; } else if (B == 12) { cout << 31 << "\n"; } return 0; }【复杂度与思考】
- 时间复杂度:。虽然代码很长,但程序只会选择其中一条分支执行,最多做十来次判断,属于常数时间。
- 空间复杂度:。只用了
A和B两个变量。 - 代码优化建议:在考场上,手敲这么长的代码不仅浪费时间,而且极其容易因为复制粘贴(Ctrl+C/V)忘记修改数字,导致莫名其妙的低级丢分(比如把 11月错敲成 31天)。我们需要合并同类项。
版本二:逻辑合并版 —— 分组判断法
把有相同特征的月份用逻辑或(
||)连接起来,大大缩减了代码的行数。#include <iostream> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int A, B; cin >> A >> B; // 先把闰年的判断单独抽出来,让逻辑更清晰 // is_leap 为 true 代表是闰年,false 代表是平年 bool is_leap = ((A % 4 == 0 && A % 100 != 0) || (A % 400 == 0)); // 组别1:所有的“大月”(31天) if (B == 1 || B == 3 || B == 5 || B == 7 || B == 8 || B == 10 || B == 12) { cout << 31 << "\n"; } // 组别2:所有的“小月”(30天) else if (B == 4 || B == 6 || B == 9 || B == 11) { cout << 30 << "\n"; } // 组别3:特殊的2月 else if (B == 2) { if (is_leap) { cout << 29 << "\n"; } else { cout << 28 << "\n"; } } return 0; }【复杂度与思考】
- 时间复杂度:。
- 空间复杂度:。多用了一个布尔变量
is_leap,依然是常数级别。 - 代码优化建议:这段代码已经非常清爽了,也是大部分同学能写出的标准答案。但在真正的竞赛高手眼里,写一长串
B == x || B == y依然不够优雅。如果有一种方法可以“直接去抽屉里拿答案”就好了。
版本三:竞赛标答版 —— “打表法”(数组查表)极简最优
在算法竞赛中,遇到这种**“输入一个小范围的整数,输出一个固定的对应值”的题目,最强大的武器就是数组打表**(Look-up Table)。我们把月份的天数预先存在数组里,把复杂的条件分支转化为直接的数组访问。
#include <iostream> using namespace std; // 关键点1:将月份的天数预埋在数组中。 // 数组大小开 13,是为了让下标 1~12 刚好对应 1月~12月,下标 0 的位置废弃不用(填0即可)。 // 这种技巧叫 "1-based indexing"(1-based 索引),能避免脑子绕弯计算下标。 const int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int A, B; cin >> A >> B; // 关键点2:优雅地判断闰年。 // 如果是闰年且月份是2月,说明需要特判输出 29 if (B == 2 && ((A % 4 == 0 && A % 100 != 0) || (A % 400 == 0))) { cout << 29 << "\n"; } else { // 其他所有情况(包括平年的2月,以及所有的1、3~12月),直接从数组中取答案! cout << days[B] << "\n"; } return 0; }【复杂度与思考】
- 时间复杂度:绝对的 。没有任何冗长的
if-else跳转逻辑,直接读取内存地址,这是 CPU 执行最快的方式。 - 空间复杂度:。使用了一个长度为 13 的整型数组,占用 字节内存,微乎其微。
- 教练点评:版本三是你在赛场上追求的终极写法!它不仅代码量最少,出错概率也极低(你只需要核对一遍数组里的数字对不对即可)。**打表思维(空间换时间 / 空间换代码量)**是贯穿整个 OI 学习生涯的核心思想之一。以后的复杂搜索题、动态规划题的边界初始化,都会大量运用到这种思维。
赶紧在你的电脑上把第三个版本敲一遍,体会一下“查表”带来的清爽感吧!有什么不懂的随时问我。
-
0
你好!很高兴再次以教练的身份指导你。这道题是一道非常经典的条件分支和逻辑判断入门题,也是以后做复杂“日期模拟类”题目的基石。我们一步步来拆解。
1. 教练的思路提示(不提供完整代码)
- 提示一:分类讨论。一年有12个月,大部分月份的天数是固定的。你可以先试着把它们分成几类?比如“总是31天的”、“总是30天的”,还有“特殊的”。
- 提示二:揪出“特殊月份”。几月份的天数是会变化的?没错,就是2月。2月的天数完全取决于当前年份是平年还是闰年。
- 提示三:闰年的判定法则。还记得小学数学里怎么判断闰年吗?口诀是:“四年一闰,百年不闰,四百年再闰”。在编程里,这句话要如何用“求余数(
%)”和“逻辑与(&&)、逻辑或(||)”翻译出来?
2. 预备知识点总结
要完美拿下这道题,你需要掌握以下几个知识点:
- 常识储备:每个月对应的天数,以及闰年的精确判断规则。
- 分支控制语句:熟练使用
if - else if - else或者switch - case语句进行多条件分支判断。 - 逻辑运算符:熟练掌握
&&(逻辑与,表示“并且”)、||(逻辑或,表示“或者”)、!=(不等于)。 - 取模运算:使用
%判断一个数能否被另一个数整除(比如A % 4 == 0)。
3. 启发式与图形式的草稿纸推导(教学模拟)
“来,拿出草稿纸。我们不要急着写代码,先把关于月份的逻辑画个清晰的图表。”
第一步:画图给月份分组(寻找规律)
教练启发:“如果让你写很多个
if去判断1到12月,代码会又长又容易错。我们把相同的月份合并起来吧。”[草稿纸推导] 月份分组 大月 (31天) : 1, 3, 5, 7, 8, 10, 12 (口诀: 一三五七八十腊,三十一天永不差) 小月 (30天) : 4, 6, 9, 11 特殊月(??天) : 2第二步:攻克2月(画判断流程图)
教练启发:“只有在输入的月份 的时候,我们才需要关心年份 。我们用流程图来表示闰年规则:”
[草稿纸推导] 闰年判断逻辑树 (判断年份 A) [A 能被 400 整除吗?] / \ 是(Yes) 否(No) / \ 【闰年! 29天】 [A 能被 100 整除吗?] / \ 是(Yes) 否(No) / \ 【平年! 28天】 [A 能被 4 整除吗?] / \ 是(Yes) 否(No) / \ 【闰年! 29天】 【平年! 28天】教练提问:“你能把这棵树合并成一行逻辑表达式吗?” 学生思考后写出:“是闰年的条件:
(A 能被 4 整除 并且 A 不能被 100 整除) 或者 (A 能被 400 整除)。”第三步:复杂度的思考与“代码优雅度”优化
- 空间复杂度:
- 我们只需要两个变量 和 来接收输入。不论数据多少,变量数量恒定。空间复杂度为 。
- 时间复杂度:
- 程序只需要进行两三步
if-else判断或简单的算术运算。没有循环结构(没有for或while)。时间复杂度为 ,这也是计算机执行最快的时间复杂度。
- 程序只需要进行两三步
- 代码层面的“优化”建议(进阶技巧):
- 既然时间已经是最优的 了,那怎么优化呢?优化代码的简洁度!
- 初学者可能会写一堆
if (B == 1 || B == 3 || B == 5 ...)。 - 教练支招(打表法):在遇到固定的映射关系时,可以用一个数组来存天数!
比如定义:
int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};这样一来,只要判断是闰年且 ,我们就输出days[2] + 1,否则直接输出days[B]。这叫“空间换时间/空间换代码量”,是极其优秀的竞赛思维!
4. 读题时的“题眼”(关键词)提取技巧
处理此类题目,抓住以下几个关键词就能瞬间破题:
- “平年和闰年” —— 题眼:这是题目在明示你核心考察点!暗示你必须动用
% 4、% 100、% 400这三个关键除数来做判断。 - “,” —— 题眼:
- 年份 的范围只有 1000 年,且在公元后,说明不仅不会遇到负数年份,也不会超出普通整型
int的范围。 - 意味着测试数据里肯定包含 (能被400整除)和 (能被4整除但不能被100整除的平年陷阱)这种关键边界数据。
- 年份 的范围只有 1000 年,且在公元后,说明不仅不会遇到负数年份,也不会超出普通整型
- “一个日期的年、月” —— 题眼:注意输入的顺序!先输入的是 (年),后输入的是 (月)。在写
cin >> A >> B;时千万别写反了,这是考场上因为粗心最容易丢分的地方。
现在,顺着流程图和打表法的思路,去草稿纸上试着把核心的几行代码写出来吧!理清了再上机敲,会有事半功倍的效果。
- 1
信息
- ID
- 13918
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 1
- 标签
- 递交数
- 7
- 已通过
- 4
- 上传者