题解:洛谷 P3131 Subsequences Summing to Sevens

【题目来源】

洛谷:P3131 [USACO16JAN] Subsequences Summing to Sevens S - 洛谷

【题目描述】

Farmer John 的 \(N\) 头奶牛站成一排,这是它们时不时会做的事情。每头奶牛都有一个独特的整数 ID 编号,以便 Farmer John 能够区分它们。Farmer John 希望为一组连续的奶牛拍照,但由于童年时与数字 \(1 \ldots 6\) 相关的创伤事件,他只希望拍摄一组奶牛,如果它们的 ID 加起来是 7 的倍数。

请帮助 Farmer John 确定他可以拍摄的最大奶牛组的大小。

【输入】

输入的第一行包含 \(N\)\(1 \leq N \leq 50,000\))。接下来的 \(N\) 行每行包含一头奶牛的整数 ID(所有 ID 都在 \(0 \ldots 1,000,000\) 范围内)。

【输出】

请输出 ID 之和为 7 的倍数的最大连续奶牛组中的奶牛数量。如果不存在这样的组,则输出 0。

【输入样例】

7
3
5
1
6
2
14
10

【输出样例】

5

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛的ID序列,求ID之和为 \(7\) 的倍数的最大连续子数组长度。这是一个前缀和 + 同余定理问题,关键在于利用 \((sum[r] - sum[l-1]) \% 7 = 0 \Leftrightarrow sum[r] \% 7 = sum[l-1] \% 7\) 的性质,将问题转化为找相同余数的最远位置对。

  2. 算法选择

    • 前缀和取模:计算前缀和数组并对 \(7\) 取模,\(a[i] = (\sum_{j=1}^{i} ID_j) \% 7\)
    • 同余配对:若 \(a[r] = a[l-1]\),则子数组 \([l, r]\) 的和为 \(7\) 的倍数,长度为 \(r - (l-1) = r - l + 1\)
    • 最远距离统计:对每个余数 \(0\)\(6\),找最左和最右出现位置,计算最大间距
  3. 关键步骤

    • 读取输入\(N\)\(N\) 个ID
    • 前缀和取模\(i\)\(1\)\(N\)):
      • \(a[i] = (a[i-1] + ID_i) \% 7\)
    • 统计最远距离(对每个余数 \(i\)\(0\)\(6\)):
      • \(i = 0\):找最右的 \(a[j] = 0\),长度 = \(j\)(从 \(1\)\(j\)
      • \(i = 1\)\(6\)
        • 从左找第一个 \(a[j] = i\) 的位置 \(l\)
        • 从右找最后一个 \(a[j] = i\) 的位置 \(r\)
        • 若都存在:\(maxn = \max(maxn, r - l)\)
    • 输出 \(maxn\)(若不存在则输出 \(0\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),前缀和 \(O(N)\),找最远距离 \(O(7 \cdot N) = O(N)\)
    • 空间复杂度:\(O(N)\),前缀和数组
  5. 前缀和同余的核心思想

    • 同余定理\(sum[l..r] \equiv 0 \pmod{7} \Leftrightarrow sum[r] \equiv sum[l-1] \pmod{7}\),将区间和判断转化为单点值相等判断
    • 最远位置对:相同余数的最左和最右位置构成最长合法子数组,因为中间任意同余位置对长度都不超过该对
    • 余数 \(0\) 特判:前缀和本身为 \(7\) 的倍数时,子数组从 \(1\) 开始,只需找最右位置
    • 适用于"模意义下最长连续子数组"类问题,核心是利用前缀和同余性质将区间问题转化为位置匹配问题

【解题思路】

【算法标签】

普及- #前缀和

【代码详解】

#include <bits/stdc++.h>
using namespace std;
long long a[50005];
int n, maxn=-1;
int main()
{
    cin >> n;  // 输入n
    for (int i=1; i<=n; i++) {  // 记录前缀和
        int tmp;
        cin >> tmp;
        a[i] = a[i-1] + tmp;
    }
    for (int i=1; i<=n; i++) {  // 对前缀和求7的余数
        a[i] = a[i]%7;
    }
    for (int i=0; i<=6; i++) {  // 分别判断0-6的余数
        int l=0, r=0;  // 定义左端点和右端点
        if (i==0) {  // 对于余数为0的情况
            for (int j=n; j>=1; j--) {  // 只需要从右向左查找余数为0的j
                if (a[j]==i) {
                    r = j;
                    break;
                }
            }
            maxn = max(maxn, r-l);  // 找到后求最大值
            continue;  // 继续其他余数的判断
        }
        for (int j=1; j<=n; j++) {  // 对于1-6的余数,需要分别从左向右找
            if (a[j]==i) {
                l = j;
                break;
            }
        }
        for (int j=n; j>=1; j--) {  // 和从右向左找
            if (a[j]==i) {
                r = j;
                break;
            }
        }
        if (l!=0 && r!=0) {
            maxn = max(maxn, r-l);  // 找到后进行计算(两个都写不等于0)
        }
    }
    if (maxn==-1) cout << 0;
    else cout << maxn << endl;
    return 0;
}

【运行结果】

7
3
5
1
6
2
14
10
5
posted @ 2026-08-28 15:57  团爸讲算法  阅读(5)  评论(0)    收藏  举报