题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 个月的天数 \(a_i\),求所有满足以下两个条件的正整数 \(L\) 之和:

    • 每个月至少有 \(4\) 周:\(a_i / L \geq 4\),即 \(L \leq a_{\min} / 4\)
    • \(a_i \bmod L\) 至多有 \(3\) 个不同值

    本质上是数学分析 + 因数枚举问题:利用"模 \(L\) 最多 \(3\) 个不同余数"的约束,推导出 \(L\) 必为某些差值的因数。

  2. 算法选择

    • 差值因数枚举:若 \(a_i \bmod L\)\(a_j \bmod L\) 相同,则 \(L \mid (a_i - a_j)\)。最多 \(3\) 个不同余数意味着至少 \(\lceil N/3 \rceil\) 个数同余,故 \(L\) 必为某些数对差值的因数
    • 小范围枚举:只需枚举前 \(4\) 个数的两两差值的因数即可覆盖所有候选 \(L\)
  3. 关键步骤

    • 读取数据:读入 \(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(6 \cdot \sqrt{a_{\max}} \cdot N \log N)\),枚举因数并验证
    • 空间复杂度:\(O(N)\),数组存储
  5. 数学约束分析的核心思想

    • 鸽巢原理\(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
posted @ 2026-08-17 09:45  团爸讲算法  阅读(11)  评论(0)    收藏  举报