UVa 887 Revolutionary Calendar
题目分析
本题要求实现一个革命日历系统的日期差计算器。这个日历系统与传统的公历有很大不同,需要仔细理解其规则。
日历系统规则
1. 基本结构
- 一年包含 131313 个普通月份,每个月份有 282828 天
- 月份名称依次为:Alligator\texttt{Alligator}Alligator, Bog\texttt{Bog}Bog, Crayfish\texttt{Crayfish}Crayfish, Damp\texttt{Damp}Damp, Eel\texttt{Eel}Eel, Fen\texttt{Fen}Fen, Gumbo\texttt{Gumbo}Gumbo, Hurricane\texttt{Hurricane}Hurricane, Inundation\texttt{Inundation}Inundation, Jaguar\texttt{Jaguar}Jaguar, Kudzu\texttt{Kudzu}Kudzu, Lake\texttt{Lake}Lake, Marsh\texttt{Marsh}Marsh
- 日期编号从 000 到 272727
- 年末有一个 Newt\texttt{Newt}Newt 月份,只有 111 天:0-Newt0\texttt{-Newt}0-Newt
2. 特殊循环系统
普通日期的天和月是同时循环的,类似于玛雅历法:
- 第一天:0-Alligator0\texttt{-Alligator}0-Alligator
- 第二天:1-Bog1\texttt{-Bog}1-Bog
- 第三天:2-Crayfish2\texttt{-Crayfish}2-Crayfish
- …
- 第 131313 天:12-Marsh12\texttt{-Marsh}12-Marsh
- 第 141414 天:13-Alligator13\texttt{-Alligator}13-Alligator
- 依此类推…
这意味着每个 (day,month)(day, month)(day,month) 组合在 13×28=36413 \times 28 = 36413×28=364 天内出现且仅出现一次。
3. 闰年规则
- 每 282028202820 年中有 683683683 个闰年
- 判断公式:((683×y)mod 2820)<683((683 \times y) \mod 2820) < 683((683×y)mod2820)<683
- 闰年会在年末增加一个 Overflow\texttt{Overflow}Overflow 月份,该月只有一天
- Overflow\texttt{Overflow}Overflow 日的编号从 000 到 682682682,对应着该年是 282028202820 年周期中的第几个闰年
4. 日期合法性检查
- 普通月份:daydayday 必须在 [0,27][0, 27][0,27] 范围内
- Newt\texttt{Newt}Newt 月:daydayday 必须为 000
- Overflow\texttt{Overflow}Overflow 月:必须满足:
- 该年确实是闰年
- daydayday 等于该年在闰年周期中的索引
- daydayday 在 [0,682][0, 682][0,682] 范围内
算法设计要点
1. 预计算优化
由于数据规模较大(年份最多 199999919999991999999),必须使用预计算来避免超时:
- 普通日期映射表:预计算每个 (monthIndex,day)(monthIndex, day)(monthIndex,day) 组合在一年中的位置(000 到 363363363)
- 闰年信息表:预计算 282028202820 年周期内每个年份是否是闰年
- 周期天数表:预计算 282028202820 年周期内每年的累计天数
- 月份前缀表:预计算所有月份名称的所有可能前缀映射,支持唯一前缀匹配
2. 日期转换算法
将日期转换为从 0-Alligator-00\texttt{-Alligator-}00-Alligator-0 开始的总天数:
-
整年天数计算:
整年天数 = 完整周期数 × 周期总天数 + 剩余年份的累计天数 -
当年天数计算:
- 普通日期:查预计算表得到位置
- Newt\texttt{Newt}Newt:364364364
- Overflow\texttt{Overflow}Overflow:365365365
3. 时间复杂度优化
通过预计算,所有关键操作都达到 O(1)O(1)O(1) 复杂度:
- 日期解析:O(1)O(1)O(1)
- 合法性检查:O(1)O(1)O(1)
- 天数计算:O(1)O(1)O(1)
参考代码
// Revolutionary Calendar
// UVa ID: 887
// Verdict: Accepted
// Submission Date: 2025-10-25
// UVa Run Time: 0.080s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <bits/stdc++.h>using namespace std;// 月份名称
const vector<string> monthNames = {"Alligator", "Bog", "Crayfish", "Damp", "Eel", "Fen", "Gumbo","Hurricane", "Inundation", "Jaguar", "Kudzu", "Lake", "Marsh","Newt", "Overflow"
};const int commonMonthsCount = 13;
const int daysPerCommonMonth = 28;
const int totalCommonDays = commonMonthsCount * daysPerCommonMonth; // 364
const int daysPerNewt = 1;
const int daysPerLeapYear = totalCommonDays + daysPerNewt + 1; // 366
const int daysPerNormalYear = totalCommonDays + daysPerNewt; // 365const int cycleYears = 2820;
const int cycleLeaps = 683;// 预计算:普通日期到天数的映射表
vector<vector<int>> commonDateToIndex;
// 预计算:闰年周期内的天数
vector<long long> cycleDays;
// 预计算:每个年份是否是闰年
vector<bool> isLeapCache;
// 预计算:月份名称前缀映射表
map<string, int> monthPrefixMap;struct Date {int day;int monthIndex; // 0-12: common, 13: Newt, 14: Overflowint year;bool isCommonMonth() const { return monthIndex >= 0 && monthIndex < commonMonthsCount; }bool isNewt() const { return monthIndex == commonMonthsCount; }bool isOverflow() const { return monthIndex == commonMonthsCount + 1; }
};// 转换为小写
string toLower(const string& str) {string result;for (char c : str) {result += tolower(c);}return result;
}// 初始化预计算数据
void initPrecomputation() {// 预计算普通日期映射表commonDateToIndex.resize(commonMonthsCount, vector<int>(daysPerCommonMonth, -1));for (int n = 0; n < totalCommonDays; ++n) {int day = n % daysPerCommonMonth;int monthIndex = n % commonMonthsCount;commonDateToIndex[monthIndex][day] = n;}// 预计算闰年信息isLeapCache.resize(cycleYears);vector<int> leapIndices(cycleYears, -1);int leapCount = 0;for (int year = 0; year < cycleYears; ++year) {isLeapCache[year] = ((683 * year) % 2820) < 683;if (isLeapCache[year]) {leapIndices[year] = leapCount;leapCount++;}}// 预计算2820年周期的总天数cycleDays.resize(cycleYears + 1);cycleDays[0] = 0;for (int year = 0; year < cycleYears; ++year) {cycleDays[year + 1] = cycleDays[year] + (isLeapCache[year] ? daysPerLeapYear : daysPerNormalYear);}// 预计算月份名称前缀映射表for (int i = 0; i < (int)monthNames.size(); ++i) {string lowerMonth = toLower(monthNames[i]);// 为每个月份名称生成所有唯一前缀for (int len = 1; len <= (int)lowerMonth.length(); ++len) {string prefix = lowerMonth.substr(0, len);// 如果此前缀已经存在,说明有冲突,标记为-1if (monthPrefixMap.find(prefix) != monthPrefixMap.end()) {if (monthPrefixMap[prefix] != -1) {// 之前映射到某个有效月份,现在发现冲突,标记为无效monthPrefixMap[prefix] = -1;}} else {// 首次出现,记录映射monthPrefixMap[prefix] = i;}}}
}// 闰年判断(使用缓存)
bool isLeapYear(int year) {return isLeapCache[year % cycleYears];
}// 该年是第几个闰年(从0开始,使用缓存)
int getLeapIndex(int year) {int y = year % cycleYears;int count = 0;for (int i = 0; i <= y; ++i) {if (isLeapCache[i]) count++;}return count - 1;
}// 月份名称转索引(使用预计算的前缀表)
int monthNameToIndex(const string& name) {string lowerName = toLower(name);auto it = monthPrefixMap.find(lowerName);if (it != monthPrefixMap.end() && it->second != -1) {return it->second;}return -1; // 不唯一或没找到
}// 解析日期
Date parseDate(const string& dateStr) {for (char c : dateStr) {if (c == '-') {size_t firstDash = dateStr.find('-');size_t secondDash = dateStr.find('-', firstDash + 1);if (secondDash == string::npos) return {-1, -1, -1};int day = stoi(dateStr.substr(0, firstDash));string monthStr = dateStr.substr(firstDash + 1, secondDash - firstDash - 1);int year = stoi(dateStr.substr(secondDash + 1));int monthIndex = monthNameToIndex(monthStr);return {day, monthIndex, year};}}return {-1, -1, -1};
}// 检查普通日期的合法性
bool isValidCommonDate(int day, int monthIndex) {if (monthIndex < 0 || monthIndex >= commonMonthsCount) return false;if (day < 0 || day >= daysPerCommonMonth) return false;return commonDateToIndex[monthIndex][day] != -1;
}// 检查日期合法性
bool isValidDate(const Date& date) {if (date.year < 0 || date.year > 1999999) return false;if (date.monthIndex < 0) return false;if (date.isCommonMonth()) {return isValidCommonDate(date.day, date.monthIndex);} else if (date.isNewt()) {return date.day == 0;} else if (date.isOverflow()) {if (date.day < 0 || date.day >= cycleLeaps) return false;if (!isLeapYear(date.year)) return false;return date.day == getLeapIndex(date.year);}return false;
}// 快速计算从 0-Alligator-0 到该日期的总天数
long long dateToDays(const Date& date) {long long totalDays = 0;// 使用周期优化计算整年天数int fullCycles = date.year / cycleYears;int remainingYears = date.year % cycleYears;totalDays = fullCycles * cycleDays[cycleYears];totalDays += cycleDays[remainingYears];// 计算当年内的天数if (date.isCommonMonth()) {totalDays += commonDateToIndex[date.monthIndex][date.day];} else if (date.isNewt()) {totalDays += totalCommonDays;} else if (date.isOverflow()) {totalDays += totalCommonDays + daysPerNewt;}return totalDays;
}int main() {// 初始化预计算数据initPrecomputation();string line;while (getline(cin, line)) {if (line.empty()) continue;stringstream stringStream(line);string firstDateStr, secondDateStr;stringStream >> firstDateStr >> secondDateStr;if (firstDateStr.empty() || secondDateStr.empty()) {cout << "eh?" << endl;continue;}Date firstDate = parseDate(firstDateStr);Date secondDate = parseDate(secondDateStr);if (!isValidDate(firstDate) || !isValidDate(secondDate)) {cout << "eh?" << endl;continue;}long long firstDateDays = dateToDays(firstDate);long long secondDateDays = dateToDays(secondDate);long long difference = secondDateDays - firstDateDays;if (difference < 0) {difference = -difference;}cout << difference << endl;}return 0;
}
总结
本题的关键在于理解革命日历的特殊规则,特别是"天月同时循环"的系统。通过巧妙的预计算和周期优化,我们能够在 O(1)O(1)O(1) 时间内完成日期解析、合法性检查和天数计算,从而高效处理大规模数据。这种预计算思想在处理复杂日历系统和周期性问题时非常有效。
