分析
求区间和,可以通过前缀和来求出。$sum[r]-sum[l-1]$就是区间[l,r]的和。如果区间[l,r]的和是k的倍数则有$(sum[r] - sum[l-1])%k = 0$,即$sum[r]%k=sum[l-1]%k$。因此,我们可以得到一个结论,对前缀和取模之后,两个相等的前缀和就能组成一个k倍区间。
有了这个结论之后,我们就可以使用两层for循环来计数k倍区间的个数,但是由于数据比较大,我们不能这样做。那么我们能不能在计算前缀和的过程中同时来统计k倍区间的个数呢?当然可以。我们可以用一个数组cnt,规定cnt[i]表示当前位置之前,前缀和取模后等于i的个数,以后每出现一次前缀和(取模后)和它相等,那么k倍区间就加上cnt[sum[i]],然后cnt[sum[i]]++。这样似乎不容易理解,我们用样例举个例子。
对于数列 1 2 3 4 5,k = 2
-
对前1个数的和模k后为1,在此之前有0个前缀和取模后为1,总个数+0
-
对前2个数的和模k后为1,在此之前有1个前缀和取模后为1,总个数+1
-
对前3个数的和模k后为0,在此之前有0个前缀和取模后为0, 总个数+0
-
对前4个数的和模k后为0,在此之前有1个前缀和取模后为0,总个数+1
-
对前5个数的和模k后为1,在此之前有2个前缀和取模后为1,总个数+2
但是我们还忽略了一点,就是我们这样做我们少计算了区间$[0,i]$构成的k倍区间,其个数为cnt[0]。
C++ 代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, k;
int sum[100005], cnt[100005];
long long ans = 0;
int main()
{
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++)
{
int x;
scanf("%d", &x);
sum[i] += (sum[i - 1] + x) % k;// 求前缀和
ans += cnt[sum[i]];// 加上在此之前与它同余的前缀和(模k后)
cnt[sum[i]]++;// 对前缀和模k后的余数统计出现次数
}
printf("%lld", ans + cnt[0]);
return 0;
}
简单来讲,sum[r] % k 和 sum[l-1] % k 的余数如果相等,那么sum[r] - sum[l-1]的差值必然是k的倍数 ,比如说
13 % 7 和 20 % 7 (20-13)%7 =0;
没讲清楚,为什么前缀和模为1的时候会影响模为0的值,我认为是这样:总共出现了3次模为1的情况,而每两次模为1组合起来可以模0,比如1,2加起来模为1,1,2,3,4,5加起来模为1,那么这两种情况组合起来(区间做减)是3,4,5就是模为0的情况。所以一共出现了3次模为1的情况,那么两两组合的情况一共有三种,再加上本来模为0的情况有3次,一共就6次模为0的情况。“k倍区间就加上cnt[sum[i]]”只是实现了计算模不为0的时候的情况两两组合的组合数量。
解释的很棒!
赞
老哥 本来模0的情况不是只有2.4两种吗 请问为什么有三种没想明白 望指导
tql
不是按照结果来想模零,是按前缀和做差来想
①1=1②1+2=3③1+2+3=6④1+2+3+4=10⑤1+2+3+4+5=15
3次模为1的情况是①②⑤
两两组合后模为0,即:②-①=2;⑤-①=2+3+4+5=14;⑤-②=3+4+5=12
本来模为0的情况有③④,组合后得到4,即一共3种情况
3+3=6
应该是这个意思吧(O。O)……
写得很棒!点个赞~
### 牛逼,就你的让我看懂了
%%%
写得真好,解释我拿了呀
悟了悟了
ans + cnt[0]
,,一写这个瞬间悟了。。。可以解释一下这个题用到的数学知识吗?
另外为什么要让cnt【0】=1啊?
如果区间[l,r]的和是k的倍数则有(sum[r]−sum[l−1]),即sum[r] 这句话是什么意思?怎么一减就成了sum[r]
感谢博主的过程分析!
知道了,i=a%c<c , j=b%c<c, i-j<c,故.. 打扰了
(a - b) % p = (a % p - b % p + p) % p (2)
模运算与基本四则运算有些相似,但是除法例外。其规则如下:
(a + b) % p = (a % p + b % p) % p (1)
(a - b) % p = (a % p - b % p + p) % p (2)
(a * b) % p = (a % p * b % p) % p (3)
(a^b) % p = ((a % p)^b) % p (4)
对前缀和取模之后,两个相等的前缀和就能组成一个k倍区间的结论是怎么得出的?
您好,” 我们少计算了区间[0,i][0,i]构成的k倍区间,其个数为cnt[0] ” cnt[0] 不是很理解?
就是之前每次累加答案的时候都没有考虑0-i这个区间也满足题意的情况(部分和左区间要-1,但是那样就会越界)。
数组左边多加一格写零就可以不用这样