题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 头奶牛的ID序列,求ID之和为 \(7\) 的倍数的最大连续子数组长度。这是一个前缀和 + 同余定理问题,关键在于利用 \((sum[r] - sum[l-1]) \% 7 = 0 \Leftrightarrow sum[r] \% 7 = sum[l-1] \% 7\) 的性质,将问题转化为找相同余数的最远位置对。
-
算法选择:
- 前缀和取模:计算前缀和数组并对 \(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\),找最左和最右出现位置,计算最大间距
-
关键步骤:
- 读取输入:\(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\))
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),前缀和 \(O(N)\),找最远距离 \(O(7 \cdot N) = O(N)\)
- 空间复杂度:\(O(N)\),前缀和数组
-
前缀和同余的核心思想:
- 同余定理:\(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
浙公网安备 33010602011771号