题目描述
OJ地址 http://lx.lanqiao.cn/problem.page?gpid=T444
给定一个长度为N的数列,A1, A2, … AN,如果其中一段连续的子序列Ai, Ai+1, … Aj(i <= j)之和是K的倍数,我们就称这个区间[i, j]是K倍区间。
你能求出数列中总共有多少个K倍区间吗?
输入:
第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100000)
输出:
输出一个整数,代表K倍区间的数目。
样例输入1:
5 2
1
2
3
4
5
样例输出1:
6
思路:
假设前缀和数组为a,如果 a[i] % k == 1,a[j] % k == 1,那么i到 j-1肯定构成一个k倍区间了,a[i]和a[j]对k取模余数相同,那么他们两个做差的话,余数部分肯定减没了,剩下的部分一定是K的倍数。
用一个数组b来记录a[i]出现的次数,例如,之前a[i]出现了5次,那么第六次再出现a[i]的时候,第六次的这个a[i]可以和前边5个a[i]分别构成一个k倍区间,这就构成了5个了,然后b[a[i]]++,也就是记录a[i]出现的次数增加1次。
另外要注意的是,前缀和对k取模等于0的时候,比如a[i] % k == 0,有两种情况,第一种是i和前边说的一样,之前出现了几次0,就会构成几个k倍区间。另一种就是,a[i]本身就是一个k倍区间,即 数组中,前i个数加起来就是一个k倍区间。所以,遇到模k等于0的时候要在答案中多记录一次。
代码
#include <bits/stdc++.h>
using namespace std;
long long a[100005],sum[100005];
int n;
int main(){
int k;
long long ans = 0;
cin >> n >> k;
for(int i = 1;i <= n; i++){
long long s;
scanf("%d",&s);
sum[i] = (sum[i-1] + s) % k;
if(sum[i] == 0) ans++;
ans = ans + a[sum[i]]; // 这个余数,原来出现过5次,那又出现了的话(第六次),就可以构成5个k倍区间
a[sum[i]]++; // 出现次数增加
}
cout << ans << endl;
return 0;
}