题目
给定一个长度为 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: 判断k倍区间
首先我们都知道需要计算前缀和才能在O(1)的情况下得到区间和,然后判断区间和模上k是否等于0得到区间否是是k的倍数。
y总用了一个更加巧妙的办法,即只要前缀和区间头尾两个数的模相同,那么这个区间就是k倍区间。推导:(s[r] - s[l-1])%k
-> s[r]%k == s[l-1]%k
要点2:cnt数组
cnt
数组其实是一个动态的数组,存的是第1i-1的数在枚举到第i个数时的模为0k-1的个数情况,即cnt[x] = num
(x模数,num个数)。
那为什么要用这种动态数组,因为我们的本意是在1~i-1中找到和i模数相同数的个数,正常情况要找这个个数我们一般会想到用俩重循环,但是这是非常浪费的,所以我们开一个数组cnt来存个数,一边走一边加,这样就可以一直保证当前的cnt数组就是枚举到当前第i个数时特有的一个数组
要点3:cnt[0]的初始化
要知道cnt[0]初始化为1的原因就一定要理解cnt这个数组到底是干嘛的,再再啰嗦一遍,cnt数组就是存模为x的数的个数,这个是cnt数组的含义。
但是cnt数组还要保证res的累加,res是根据当前cnt数组存的个数计数的,那么我们想想第i个数模是0,那么它除了和前i个数组成n个k倍区间,是不是自己也单独是一个k倍区间,所以所有模为0的数的真正的区间个数都是n+1,所以直接给cnt[0]初始化为1,那么所有模为0的数在累加res时都会自动+1
既然我们已经理解初始化的原因就是因为模为0的数自己也是一个区间要加1,所以可以改写成如下代码:
LL res=0;
//cnt[0]=1; //不进行初始化
for(int i=1;i<=n;i++)
{
res+=cnt[s[i]%k];
if(s[i]%k==0) res++; //直接对模0的数特判res+1即可
cnt[s[i]%k]++;
}
代码:
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long int LL;
const int N=100010;
LL cnt[N],s[N];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
scanf("%d",&s[i]);
s[i]+=s[i-1];
}
LL res=0;
cnt[0]=1;
for(int i=1;i<=n;i++)
{
res+=cnt[s[i]%k];
cnt[s[i]%k]++;
}
cout<<res;
return 0;
}