[题目概述]
给定一个长度为 N的数列,
A
1
,
A
2
,
…
A
N
A_1,A_2,…A_N
A1,A2,…AN,如果其中一段连续的子序列
A
i
…
A
j
A_i…A_j
Ai…Aj 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K倍区间。
你能求出数列中总共有多少个 K倍区间吗?
输入格式
第一行包含两个整数 N和 K。以下N行每行包含一个整数 A i A_i Ai。
输出格式
输出一个整数,代表 K倍区间的数目。
数据范围
1 ≤ N, K ≤100000,
1 ≤
A
i
A_i
Ai ≤100000
- 分析问题
- 一眼就可看出这个题考察前缀和,可以别枚举左右端点来求中间的和,但结合题目所给数据范围1 ≤ N ≤100000 可知,我们最多可以枚举一次,否则大概率会超时,那么怎样进行优化呢?
- 我们此时的第二重循环就是当i 固定时,在1 ~ i 中有多少s[j - 1]与s[i] 对k的余数相同 (同余定理:数论中的重要概念。给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对m同余),稍加变换就是求0 ~ i - 1中有多少s[j]与s[i] 对k的余数相同,也就是在所有前缀和中,有多少前缀和是余数相同的,这样就优化了一重循环。
for(int i = 1; i <= n; i ++) for(int j = 1; j <= i; j ++) if((s[i] - s[j - 1]) % k == 0) cnt ++;
- 完整代码(详解版)
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 100005;
int n, k;
// 最多10^5个数,每个数最大10^5,而int最大能接受10^9,所以可能会爆int,需要用long long
// cnt[ ]记录相同余数的数有多少个
long long s[N], a[N], cnt[N];
int main() {
cin >> n >> k;
// 预处理前缀和
for (int i = 1; i <= n; i ++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
int res = 0;
cnt[0] = 1; // 余数为0的数本来就有一个,需要初始化为1(子序列为空时)
for (int i = 1; i <= n; i ++) {
res += cnt[s[i] % k];
cnt[s[i] % k] ++; // 当前前缀和的余数也是一种情况,需要更新
}
cout << res;
return 0;
}
- 本题完毕,有问题的小伙伴可以评论区留言!有帮助的话记得点赞收藏