1. 题目背景与需求解析
这道编程题源自洛谷在线评测系统的P1424题号,属于典型的循环结构应用题。题目描述了一条小鱼在特定周期内的游泳行为模式——工作日游泳,周末休息。我们需要计算小鱼在给定的起始日和持续天数内总共游泳的距离。
1.1 问题重述
假设小鱼每周工作5天(周一到周五),每天游泳250公里;周末休息2天(周六和周日),不游泳。给定起始日是星期X(1-7分别对应周一到周日),持续n天后,计算小鱼总共游泳的距离。
1.2 核心难点
这类日期循环计算问题在实际编程中非常常见,比如:
- 工作日计算器
- 考勤系统
- 项目进度跟踪
- 周期性任务调度
关键在于如何高效处理以下两个问题:
- 星期几的循环变化(到达周日后再回到周一)
- 工作日与休息日的准确判断
2. 算法设计与思路分析
2.1 暴力模拟法
最直观的解法是逐天模拟:
python复制day = x # 起始日
total = 0
for _ in range(n):
if 1 <= day <= 5: # 工作日
total += 250
day += 1
if day > 7: # 星期循环
day = 1
注意:这种方法虽然简单,但当n很大时(比如1e9),时间复杂度O(n)会很高。
2.2 数学优化法
更高效的解法是利用数学计算:
- 计算完整周数:full_weeks = n // 7
- 计算剩余天数:remaining_days = n % 7
- 完整周的游泳距离:full_weeks * 5 * 250
- 剩余天数的游泳距离:逐个判断起始日后的remaining_days中有多少工作日
python复制full_weeks = n // 7
remaining_days = n % 7
total = full_weeks * 5 * 250
current_day = x
for _ in range(remaining_days):
if 1 <= current_day <= 5:
total += 250
current_day += 1
if current_day > 7:
current_day = 1
2.3 性能对比
| 方法 | 时间复杂度 | 适用场景 |
|---|---|---|
| 暴力模拟 | O(n) | n较小时(<1e6) |
| 数学优化 | O(1) | 任意规模的n |
3. 完整实现与边界处理
3.1 Python实现
python复制def calculate_distance(x, n):
full_weeks = n // 7
remaining_days = n % 7
total = full_weeks * 5 * 250
current_day = x
for _ in range(remaining_days):
if 1 <= current_day <= 5:
total += 250
current_day += 1
if current_day > 7:
current_day = 1
return total
x, n = map(int, input().split())
print(calculate_distance(x, n))
3.2 边界情况测试
需要特别注意的边界条件:
- 起始日是周末的情况
- n=0的情况(虽然题目保证n≥1)
- n正好是7的倍数的情况
- 起始日+剩余天数跨越周末的情况
测试用例示例:
python复制assert calculate_distance(1, 7) == 1250 # 完整一周
assert calculate_distance(6, 10) == 1250 # 从周六开始,10天后
assert calculate_distance(5, 3) == 750 # 周五开始,3个工作日
4. 算法优化进阶
4.1 剩余天数计算优化
可以进一步优化剩余天数的计算,避免逐个判断:
python复制remaining_workdays = max(0, min(5, x + remaining_days - 1) - max(1, x) + 1)
total += remaining_workdays * 250
4.2 数学公式法
完全用数学公式计算,无需循环:
python复制def calculate_distance(x, n):
full_weeks = n // 7
remaining_days = n % 7
total = full_weeks * 5 * 250
# 计算剩余天数中的工作日
first_part = max(0, min(5, x + remaining_days - 1) - x + 1)
second_part = max(0, min(5, (x + remaining_days - 1) % 7))
total += (first_part + second_part) * 250
return total
5. 同类问题扩展
这类周期性问题有很多变种,比如:
5.1 变种1:不固定休息日
如果休息日不是固定的周末,而是每工作m天后休息k天,如何计算?
解决方案:
- 计算完整周期(m+k天)的数量
- 计算剩余天数中的工作日
5.2 变种2:多休息日模式
如果休息日模式更复杂,比如:
- 大小周(一周单休,一周双休)
- 节假日调休
解决方案:
- 需要预先定义好休息日日历
- 使用查表法或规则引擎判断
5.3 实际应用案例
- 工资计算系统:计算当月实际工作天数
- 项目进度管理:计算有效工作日
- 健身计划:制定周期性训练计划
6. 编程技巧与调试心得
6.1 调试技巧
- 可视化测试:打印出每天的星期和工作状态
python复制for i in range(n):
day = (x + i - 1) % 7 + 1
working = 1 <= day <= 5
print(f"Day {i+1}: {'Work' if working else 'Rest'}")
- 边界测试:特别测试起始日是周日、n=1等情况
6.2 常见错误
- 星期循环处理错误(常见忘记重置为1)
- 工作日判断条件错误(比如写成0-4)
- 整数除法与取模运算混淆
6.3 性能优化建议
- 对于n极大的情况(如1e18),必须使用数学方法
- 避免在循环中进行不必要的条件判断
- 可以预先计算每周的工作日模式
7. 其他语言实现
7.1 C++实现
cpp复制#include <iostream>
using namespace std;
int main() {
int x, n;
cin >> x >> n;
int full_weeks = n / 7;
int remaining_days = n % 7;
int total = full_weeks * 5 * 250;
for (int i = 0; i < remaining_days; ++i) {
int current_day = x + i;
if (current_day > 7) current_day -= 7;
if (current_day >= 1 && current_day <= 5) {
total += 250;
}
}
cout << total << endl;
return 0;
}
7.2 Java实现
java复制import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
int n = sc.nextInt();
int fullWeeks = n / 7;
int remainingDays = n % 7;
int total = fullWeeks * 5 * 250;
for (int i = 0; i < remainingDays; i++) {
int currentDay = x + i;
if (currentDay > 7) currentDay -= 7;
if (currentDay >= 1 && currentDay <= 5) {
total += 250;
}
}
System.out.println(total);
}
}
8. 数学推导与证明
8.1 完整周的工作日计算
证明:在任何连续的7天中,必定包含5个工作日和2个休息日。
因此,对于full_weeks = n // 7,游泳距离必定是full_weeks * 5 * 250。
8.2 剩余天数的工作日计算
设起始日为x,剩余天数为r,则工作日数量为:
max(0, min(5, x + r - 1) - max(1, x) + 1) +
max(0, min(5, (x + r - 1) % 7))
这个公式考虑了两种情况:
- 剩余天数都在同一周内
- 剩余天数跨越到下一周
9. 实际工程应用建议
在实际项目中处理类似问题时:
- 使用成熟的日期库(如Python的datetime、Java的java.time)
- 考虑时区和夏令时(如果是全球化的系统)
- 缓存计算结果(如果频繁查询相同的时间段)
- 支持多种工作日历(考虑不同国家的节假日)
例如,使用Python的dateutil库:
python复制from dateutil.rrule import rrule, DAILY, MO, TU, WE, TH, FR
from datetime import datetime, timedelta
def workdays(start_date, days):
end_date = start_date + timedelta(days=days-1)
return len(list(rrule(DAILY, dtstart=start_date, until=end_date,
byweekday=(MO,TU,WE,TH,FR))))
10. 教学与学习建议
对于初学者学习这类问题,建议:
- 先写暴力解法,确保理解问题本质
- 画出时间线,可视化日期循环
- 小规模测试,验证边界条件
- 逐步优化,从O(n)到O(1)的算法
- 对比不同语言的实现,理解编程范式差异
在教学时可以引入:
- 模运算的实际应用
- 时间复杂度的实际影响
- 测试驱动开发(TDD)的实践