E. Calendar Ambiguity 解题思路
核心问题分析
大家遇到这种跟日期、星期几相关的题目,第一反应可能是头皮发麻。题目中说:一个外星历法,一年有 个月,每个月 天,一周有 天。我们要在合理的范围内找出一对 且 ,使得“第 个月的第 天”和“第 个月的第 天”是星期几完全一样。
面对这种题,千万不要去试图写什么大模拟!我们的首要任务是:把题目的文字游戏翻译成纯粹的数学公式。
1. 翻译题意:化为同余方程
首先,怎么算某一天是这一年的第几天? 对于“第 个月的第 天”,因为前面有 个完整的月(每个月 天),再加上当月的 天,所以距离这一年第一天的天数差是:
题目要求“第 个月的第 天”和“第 个月的第 天”星期相同。这就意味着,这两天距离第一天的天数之差,必须是一周天数 的整数倍。 我们把上面的公式代入并相减:
把式子展开并合并同类项:
$(y - x)(d - 1) \equiv 0 \pmod w$
你看!原本绕来绕去的文字,瞬间变成了一个极其清爽的同余式!
2. 数论降维:找出“步长”
上面的式子告诉我们, 乘以 必须是 的倍数。 既然 里面本身就可能自带了 的一些因子(即它们的最大公约数 ),那么 只需要“补齐” 剩下的那部分因子就可以了。
因此, 必须是下面这个数字的整数倍:
我们把这个必须的差值称为“步长” 。也就是说,。
3. 边界确定与等差数列求和
理清了差值的规律,我们还需要确定 和 的取值范围。 因为 和 既要当作“月份”,又要当作“天数”,所以它们必须同时不超过 和 。 换句话说,它们的上限 。题目又要求 。
现在问题被彻底剥离成了一道小学奥数题: 在 到 的数字中,任选两个数,使它们的差是 的倍数,共有多少对?
我们按照倍数 来分类讨论:
- 当相差 时,满足条件的有 ,一共 对。
- 当相差 时,一共 对。
- 当相差 时,一共 对。
其中,最大的倍数 。
将上面所有的对数加起来:
直接套用等差数列求和公式!算法的时间复杂度从原本可能的 甚至 ,瞬间降维打击到了 (求 的对数时间几乎可以忽略)。这才是算法最迷人的地方!
CPP 代码实现
// E. Calendar Ambiguity
#include <bits/stdc++.h>
#define lg(x) (63 - __builtin_clzll(x))
#define all(x) (x).begin(), (x).end()
#define low_bit(x) ((x) & (-x))
#define pb push_back
#define db long double
#define int long long
#define endl "\n"
using namespace std;
void solve() {
int m, d, w;
cin >> m >> d >> w;
// x 和 y 既是月份又是天数,所以最大不能超过 m 和 d 的较小值
int n = min(m, d);
// 获取 (d-1) 和 w 的最大公约数
// 这是为了算出 y-x 需要补齐的最小因子
int g = gcd(d - 1, w);
// step 就是 y 和 x 之间必须满足的最小差值(步长)
int step = w / g;
// k 表示在 1 到 n 的范围内,最大的差值能包含多少个 step
// n-1 是因为差值最大只能是 n-1(比如从 1 到 n)
int k = (n - 1) / step;
// 如果 k <= 0,说明哪怕是最短的步长都超过了 n 的范围,没有合法的对数
if (k <= 0) {
cout << 0 << endl;
return;
}
// 根据等差数列求和公式:
// sum = (n - step) + (n - 2*step) + ... + (n - k*step)
// 提取 n 得到 k * n
// 提取 step 得到 step * (1 + 2 + ... + k) = step * (k * (k + 1) / 2)
int ans = k * n - step * (k * (k + 1) / 2);
cout << ans << endl;
}
signed main() {
// 优化输入输出,竞技编程好习惯
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
cin >> t;
while (t--) {
solve();
}
}