我一开始用暴力的方式做,果不其然超时了;
观察这个条件:
(S[j] - S[i-1]) % k == 0
根据模运算的性质:
如果 (a - b) % k == 0
那么 a % k == b % k
所以
(S[j] - S[i-1]) % k == 0
⇔ S[j] % k == S[i-1] % k
这就是最重要的转化!我们把「区间和能被K整除」转化成了「两个前缀和模K同余」。
现在问题变成:
有多少对 (i-1, j) 满足 0 ≤ i-1 < j ≤ n 且 S[i-1] % k == S[j] % k
这等价于:
统计每个余数出现了多少次,然后从同余数中任选2个
也就是说,设余数 r 出现了 cnt[r] 次,那么贡献是:C(cnt[r], 2) = cnt[r] × (cnt[r] - 1) / 2
cpp
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=100010;
int n,k;
ll s[N];
ll cnt[N];
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
s[i]=s[i-1]+x;
}
cnt[0]=1;
for(int i=1;i<=n;i++)
{
cnt[(s[i]%k+k)%k]++;
}
ll res=0;
for(int i=0;i<k;i++)
{
res+=cnt[i]*(cnt[i]-1)/2;
}
cout<<res<<endl;
return 0;
}