这道题是前缀和和一点数论的运用,看到连续子段和第一时间想到的就是前缀和的解法,而且对于竞赛题而言,一般数据范围很大。所以我们都需要优化算法才能让很大的数据范围有个着落。
这里的数据范围我们可以看到是O(n)的时间复杂度。虽然我们在OI赛制中直接可以暴力得出一部分的样例分,但是对于我们的算法能力提升是微乎其微的。所以我们要进行优化。
这里给大家科普一个有些人不知道的数论知识,就是同余定理了。
同余定理_百度百科 (baidu.com)大家可以了解一下,这里我们直接在运用中去讲解。
思路:首先想到就是用前缀和的知识进行操作,让我们求区间的和与k的倍数关系。那么我们可以想,假如我们求区间[l,r],那么这个区间肯定就是al+al+1+...+ar的值,这里我们用Si来代表前缀和,简单来说就是让我们求Sr-Sl的值是k的倍数的区间有多少个的问题而已。(因为这里l,r都可以是0,所以不是Sr-Sl-1)。由同余定理我们知道,如果说(a-b)%c=0,那么也就是说像a%c=b%c的形式。也就是说,Sr%k=Sl%k这样的符合条件的有多少个区间。这就要求Sl和Sr的余数是否相等的问题了。所以,当我们选取个数的时候,需要从相同余数的区间当中任选两个,也就是数学里面的组合数计算了(这个计算可以看高斯公式,也是高中的知识点,不会的话就去查吧),而对于余数不同的区间我们可以怎么办呢?
这就需要提到我们所知道的数据结构中的一个排序问题的方法了,也是个稳定排序算法,就是基数排序问题。在基数排序中,我们会用到类似于桶排序的方法存储余数进行排序。这里我们借用桶排序的思想,利用桶来存储余数,这样我们就可以解决余数不相同的个数怎么存储的问题了。
那么接下来,就一步一步阐述代码的编写。
注意:如果不把握数据范围的话直接全写long long就行了,要不然会有一些很bug的错误出现。
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<algorithm>
#include<stack>
#include<queue>
#include<sstream>
#include<map>
#include<limits.h>
#include<set>
#define MAX 100005
#define _for(i,a,b) for(int i=a;i<(b);i++)
#define ALL(x) x.begin(),x.end()
using namespace std;
typedef long long LL;
LL a[MAX];//前缀和数组
LL tong[MAX];//这个就是桶的数组,用来存储余数是i的前缀和的个数
LL n;
void qianzhuihe(){
_for(i,1,n+1)
a[i]+=a[i-1];
}//前缀和的求解
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);//加快输入输出
LL k;
cin >> n >> k;
LL sum = 0;
tong[0]=1;//这里之所以需要为1是因为S0是存在的,而通常我们算前缀和是不算进它的,需要首先为赋值
_for(i,1,n+1)
cin>>a[i];
qianzhuihe();
_for(i, 1, n+1)
{
sum=a[i]%k;//对前缀和求余数
tong[sum]++;//存储余数是此的区间个数
}
LL count = 0;//统计总个数
_for(i, 0, k) {
count += (tong[i] * (tong[i] - 1)) / 2;//组合数计算
}
cout << count << endl;
return 0;
}