题目描述:
给定一个长度为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
资源限制
内存限制:256.0MB C/C++时间限制:1.0s
原题链接:“蓝桥杯”练习系统
解题思路:前缀和+动态规划
最简单的方法是纯暴力求解,枚举所有的子序列,但是这样做复杂度为O(n^2),会超时,无法通过所有数据。因此,我们可以对其进行优化,用S[n]存下前n个数的前缀和,当(Sr - Sl) % k == 0时,则区间[l+1...r]是K倍区间,但是用了前缀和后直接去计算,也会超时,只是减少了一部分计算量,复杂度还是O(n^2)。因此,我们还可以进行优化,我们发现(Sr - Sl) % k = 0可以转化成Sr % k = Sl % k,也就是说当Sr和Sl模k的余数相等时,区间[l+1...r]是K倍区间。因此我们可以创建一个数组remainder来保存每个Si模k的余数,对每个Si,前面(S0....Si - 1)与其模k余数相等的个数即为以第i个数结尾的子串中(相当于计算(Si - S0) % k == 0 ..... (Si - S(i - 1)) % k == 0),K倍区间的个数,把每个Si的K倍区间的个数相加即为最后的结果,由于不需要计算Sr - Sl了,因此不必存储Si,只需要在每次输入一个数后计算当前Si即可。
代码如下(C++):
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
int n,k;
cin>>n>>k;
long long sum = 0,count = 0;
int remainder[k]; //定义一个数组,remainder[i]表示Sn % k == i的个数
memset(remainder,0,sizeof remainder);
remainder[0]=1; //S0 = 0,0 % k = 0,因此S0模k的余数为0, remainder[0]初值为1
while(n--){
int num;
cin>>num;
sum += num; //计算当前Sn
count += remainder[sum % k]; //加上与当前Sn对k取模结果相同的Si的个数,即为以第i个数结尾的子串中,K倍区间的个数
remainder[sum % k]++; //当前Sn % k == i, remainder[i]个数加1
}
cout<<count<<endl;
return 0;
}