前缀和:K倍区间
K 倍区间
www.acwing.com/problem/content/1232/
-
任取一个 i i i 作为这个区间的右端点,那么就是求能令 S i − S i − k ≡ 0 S_{i}-S_{i-k}\equiv 0 Si−Si−k≡0 的 $k $ 的个数 k ∈ [ 1 , i ] k\in [1, i] k∈[1,i]。 s [ r ] % k s[r] \% k s[r]%k 和 s [ l − 1 ] % k s[l-1] \% k s[l−1]%k 的余数如果相等,那么 s [ r ] − s [ l − 1 ] s[r] - s[l-1] s[r]−s[l−1] 的差值必然是 k 的倍数。
-
S i − S i − k ≡ 0 → S i ≡ S i − k m o d k S_{i}-S_{i-k}\equiv 0 \rightarrow S_{i} \equiv S_{i-k}\ mod \ k Si−Si−k≡0→Si≡Si−k mod k 即计算 S 0 , . . . S i − 1 S_0, ...S_{i-1} S0,...Si−1 中与 S i S_i Si 模 k k k 的余数相同的个数。需要统计所有满足 s [ j ] % K = = s [ i − 1 ] % K s[j] \% K == s[i-1] \% K s[j]%K==s[i−1]%K 的 ( i , j ) (i, j) (i,j) 对。
-
创建哈希表
cnt[x]
,表示余数为 $x $ 的数的个数。假设 s[i] = 3,在后边的循环中,又出现了一个 s[i] = 3,那么此时,这个“3”可以和前边出现过的所有的“3”分别构成一个 K 倍区间,前边的“3”一共出现过cnt[s[i]]
次,所以此时又新增了cnt[s[i]]
个 K 倍区间。 -
为什么初始化
cnt[0] = 1
?-
cnt[mod]
记录的是余数为mod
的前缀和的数量。 - 初始时,
s[0] = 0
,且0 % K = 0
,所以cnt[0]
应该初始化为 1。 - 如果不初始化
cnt[0] = 1
,则会漏掉所有s[j] % K == 0
的区间(即[1, j]
的区间)。
-
import java.util.*;
public class Main {
static final int N = 100010;
static int[] cnt = new int[N];
static long[] s = new long[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
for (int i = 1; i <= n; i++) {
s[i] = sc.nextLong();
s[i] += s[i - 1];
}
cnt[0] = 1; // s[0]=0
long res = 0;
for (int i = 1; i <= n; i++) {
int mod = (int) (s[i] % k);
// 先加上与Si模k相同的数
res += cnt[mod];
// 再递增
cnt[mod]++;
}
System.out.println(res);
}
}