问题描述
给定一个长度为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倍区间的数目。
样例输入
5 2
1
2
3
4
5
样例输出
6
数据规模和约定
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 2000ms
解题思路
最简单的想法是两重for循环枚举所有区间,但是因为N能达到1e5,两重for就需要运算1e10,会超时。
我们假设从第一个数开始到第i个数的和为 sum,sum%k == rare, 那么我们只要从第一个数到第i-1个数这个区间里减去一个从第一个数开始的连续的区间和%k = rare 的区间(假设这个区间为[ 1, j ] ),则sum - sum[1, j] 即区间[ j+1, i ]的和为k的整数倍。
作法: 以数组cnt[p][i]记录区间[ 1, i ] 里区间和%k == p的从第一个数开始的连续区间的个数,以sum[ i ]记录前i个数的和,i从0到n枚举cnt[ sum[i]%k] [i-1]并加和即为所求的答案。 下面的代码是空间优化后的,为什么能优化? 自己考虑下。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int a[maxn];
long long cnt[maxn];
int main()
{
int n,k;
cin >> n >> k;
memset(cnt,0,sizeof cnt);
long long tot=0,sum=0;
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
sum += a[i];
if(sum%k == 0)
tot++;
tot += cnt[sum%k];
cnt[sum%k]++;
}
cout << tot << endl;
}