题解:洛谷 P10136 Cowlendar
【题目来源】
洛谷:P10136 [USACO24JAN] Cowlendar S - 洛谷
【题目描述】
Bessie 在一个陌生的星球上醒来。这个星球上有 \(N\)(\(1\le N\le 10^4\))个月,分别有 \(a_1,\ldots,a_N\) 天(\(1\le a_i\le 4\cdot 10^9\),所有 \(a_i\) 均为整数)。此外,这个星球上还存在周,一周为 \(L\) 天,其中 \(L\) 是一个正整数。有趣的是,Bessie 知道以下事情:
- 对于正确的 \(L\),每个月至少有 \(4\) 周。
- 对于正确的 \(L\),\(a_i\bmod L\) 至多有 \(3\) 个不同值。
不幸的是,Bessie 忘记了 \(L\) 是多少!请通过输出 \(L\) 的所有可能值之和来帮助她。
注意这个问题涉及到的整数可能需要使用 64 位整数型(例如,C/C++ 中的 "long long")。
【输入】
输入的第一行包含一个整数 \(N\)。第二行包含 \(N\) 个空格分隔的整数 \(a_1,\ldots,a_N\)。
【输出】
输出一个整数,为 \(L\) 的所有可能值之和。
【输入样例】
12
31 28 31 30 31 30 31 31 30 31 30 31
【输出样例】
28
【核心思想】
-
问题分析:给定 \(N\) 个月的天数 \(a_i\),求所有满足以下两个条件的正整数 \(L\) 之和:
- 每个月至少有 \(4\) 周:\(a_i / L \geq 4\),即 \(L \leq a_{\min} / 4\)
- \(a_i \bmod L\) 至多有 \(3\) 个不同值
本质上是数学分析 + 因数枚举问题:利用"模 \(L\) 最多 \(3\) 个不同余数"的约束,推导出 \(L\) 必为某些差值的因数。
-
算法选择:
- 差值因数枚举:若 \(a_i \bmod L\) 和 \(a_j \bmod L\) 相同,则 \(L \mid (a_i - a_j)\)。最多 \(3\) 个不同余数意味着至少 \(\lceil N/3 \rceil\) 个数同余,故 \(L\) 必为某些数对差值的因数
- 小范围枚举:只需枚举前 \(4\) 个数的两两差值的因数即可覆盖所有候选 \(L\)
-
关键步骤:
- 读取数据:读入 \(N\) 和 \(a[1..N]\),排序去重
- 特殊情况:若去重后 \(N \leq 3\),则任意 \(L \leq a_1/4\) 都满足,答案为 \(1 + 2 + \dots + \lfloor a_1/4 \rfloor\)
- 计算差值:取前 \(4\) 个数,计算所有 \(\binom{4}{2} = 6\) 个两两差值,存入 \(b[1..m]\) 并去重
- 枚举因数:对每个差值 \(b[i]\),枚举其所有因数 \(j\) 和 \(b[i]/j\),用
check(j)验证:- 若 \(a_1 / j < 4\),跳过(不满足至少 \(4\) 周)
- 计算所有 \(a_i \bmod j\),排序去重后若不同余数 \(\leq 3\) 且未统计过,\(ans \leftarrow ans + j\)
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(6 \cdot \sqrt{a_{\max}} \cdot N \log N)\),枚举因数并验证
- 空间复杂度:\(O(N)\),数组存储
-
数学约束分析的核心思想:
- 鸽巢原理:\(N\) 个数模 \(L\) 最多 \(3\) 个不同值,则至少 \(\lceil N/3 \rceil\) 个数同余,这些数的差都是 \(L\) 的倍数
- 前 \(4\) 个数的充分性:若 \(N > 3\),取前 \(4\) 个数,由鸽巢原理至少 \(2\) 个同余,其差包含 \(L\) 的因数信息
- 因数枚举的完备性:\(L\) 必为某对同余数差的因数,枚举所有差值的因数即可覆盖所有候选
- 去重与验证:用
map避免重复统计,排序去重后检查余数个数保证正确性 - 适用于数论约束、因数分解、鸽巢原理应用类问题
【解题思路】

【算法标签】
普及+ #数学
【代码详解】
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 10005, P = 1e6 + 2;
int n; // 数字个数
int cnt; // 质数计数器
int pr[P]; // 存储质数
int minp[P]; // 存储最小质因子
bool vis[P]; // 标记是否为质数
LL a[N]; // 存储输入数字
LL b[N]; // 存储数字差值
LL ans; // 最终结果
LL c[N]; // 临时存储取模结果
map<LL, bool> mp; // 记录已处理过的数
/**
* 检查x是否满足条件
* @param x 待检查的数
*/
void check(LL x)
{
// 检查x是否太小
if (a[1] / x < 4) return;
// 计算每个数对x取模的结果
for (int i = 1; i <= n; i++)
{
c[i] = a[i] % x;
}
// 排序并去重
sort(c + 1, c + n + 1);
int k = unique(c + 1, c + n + 1) - c - 1;
// 如果不同余数不超过3个且x未被处理过
if (k <= 3 && mp[x] == 0)
{
ans += x;
}
mp[x] = 1;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
}
// 排序并去重
sort(a + 1, a + n + 1);
n = unique(a + 1, a + n + 1) - a - 1;
// 特殊情况处理:当不同数字不超过3个时
if (n <= 3)
{
LL p = a[1] / 4;
printf("%lld\n", p * (p + 1) / 2);
return 0;
}
// 计算前4个数字的两两差值
int m = 0;
for (int i = 1; i < 4; i++)
{
for (int j = i + 1; j <= 4; j++)
{
b[++m] = abs(a[i] - a[j]);
}
}
// 对差值排序并去重
sort(b + 1, b + m + 1);
m = unique(b + 1, b + m + 1) - b - 1;
// 检查每个差值的所有因数
for (int i = 1; i <= m; i++)
{
for (LL j = 1; j * j <= b[i]; j++)
{
if (b[i] % j == 0)
{
check(j);
check(b[i] / j);
}
}
}
printf("%lld\n", ans);
return 0;
}
【运行结果】
12
31 28 31 30 31 30 31 31 30 31 30 31
28
浙公网安备 33010602011771号