题目描述:
给定一个长度为 N 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K 倍区间。
你能求出数列中总共有多少个 K 倍区间吗?
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行包含一个整数 Ai。
输出格式
输出一个整数,代表 K 倍区间的数目。
数据范围
1≤N,K≤100000,
1≤Ai≤100000
输入样例:
5 2
1
2
3
4
5
输出样例:
6
解题思路:
1.最简单的方法,采用暴力枚举,三层循环
for(int r = 1; r <= n; r++)
for(int l = 1; l <= n; l++)
{
int sum = 0;
for(int i = l; i <= r; i++)
{
·········计算区间和
}
判断是否可整除k
}
这个方法最简单,最容易想到,但是时间复杂度在 n 的三次方,因为数据量是100000,所以肯定不过。
2.升级版1
优化最里面一层循环,将其优化,可以另外开辟一个数组sum,存前n项和,这样,里面可以变成
sum[r]-sum[l-1]
for(int r = 1; r <= n; r++)
for(int l = 1; l <= n; l++)
{
if((sum[r]-sum[l])%k==0)
{
}
}
这样子,较为容易想到,时间复杂度在n的二次方,数据量太大,依旧过不掉,因为在十万次,所以平方次复杂度仍过高
3.极度优化
因为: (sum[r]-sum[l])%k==0)
所以:只要sum[r]%k == sum[l]%k
,则这个区间是k被区间,也就是说,只要两个模k的余数相同,即两数区间为k倍区间,所以,可以开辟数组,存余数相同的个数
AC代码:
#include <iostream>
using namespace std;
long long sum[100005];//存放前缀和
long long re[100005];//存放模k的余数
int main()
{
int n, k;
scanf("%d %d", &n, &k);
for(int i = 1; i <= n; i++)
{
scanf("%d", &sum[i]);
sum[i] += sum[i-1];//计算前缀和
}
long long res = 0;//res存放几个k倍区间
re[0] = 1;//我们的sum[0]默认是0,所以re[0] = 1,这里可能很多人不能理解,因为当余数是0的时候,其实那一个数字也可以的,
/*
例如,样例输入
5 2
1 2 3 4 5
这时候 sum数组是: 1 3 6 10 15
取模的时候是:1 1 0 0 1
但是这个时候注意:当余数0这个数字刚放进去的时候,切记,这个数字也是单个其实就是k的倍数了,所以这个re[0] = 1,为的就是把这个单个数字计算进去,
可能会有人说,那如果余数为0的,是不是多算了
你们可以试试
例如:
1 2
1
这个时候sum是:1
取模是:1
那么计算的时候在下面循环的时候不会有re[0]加进去
这就是为什么re[0]=1;
*/
for(int i = 1; i <= n; i++)
{
res += re[sum[i] % k];//以前有这么多模k相同数,这个数字到这些数字之间都是k倍区间,所以res加上这些
re[sum[i]%k]++;//最后把自己也算在相同模k数之中
}
printf("%lld", res);//最后输出结果,AC
return 0;
}
1不是2的倍数,不构成K倍区间,不加进去有啥问题