[P8649 蓝桥杯 2017 省 B] k 倍区间:前缀和 + 同余
题目描述
给定长度为 $N$ 的数组 $A$,求有多少个连续区间 $(l, r)$,满足区间和是 $K$ 的倍数。
数据范围:$1 \le N, K \le 10^5$,$1 \le A_i \le 10^5$。
一、思路:前缀和 + 同余
1.1 前缀和
定义前缀和数组 $s$,规定:
区间 $(l, r)$ 的总和:
1.2 题目条件 → 同余关系
题目要 "区间和是 $K$ 的倍数":
等价变形:
核心结论:只要两个前缀和对 $K$ 取模的余数相等,它们之间的区间和就一定是 $K$ 的倍数。
1.3 直观例子
$K = 2$,$s[r] = 15$,$s[l-1] = 3$:
- $15 \bmod 2 = 1$,$3 \bmod 2 = 1$ → 余数相同 ✔
- $s[r] - s[l-1] = 12$,12 是 2 的倍数 ✔
所以数"前缀和同余对数"就是答案。
二、暴力思路(仅用于理解)
固定左右端点枚举所有区间:
$N = 10^5$ 时复杂度 $O(N^2)$,必然超时。
三、思路:桶计数
把所有 $s[i]$ 对 $K$ 取模,余数范围是 $[0, K-1]$。同余的前缀和可以两两配对——任意两个余数相同的前缀和都能形成一个合法的 $K$ 倍区间。
桶计数:开一个大小为 $K$ 的数组
cnt,cnt[r]表示「到目前为止,前缀和模 $K$ 等于 $r$ 的出现次数」。
- 遍历到前缀和
s[i]时,先把它和前面 cnt[rem] 个同余前缀和配对;- 然后把
cnt[rem]++,把自己加入桶里供后续元素配对。
特别地,cnt[0] = 1,因为 s[0] = 0 这个虚前缀和一开始就在桶里——这处理了"从数组开头开始的区间"。
四、版本 1:分两步写(易读)
1 |
|
代码解释
s数组:s[i]保存前 $i$ 个数总和,s[0] = 0是必不可少的初始项。cnt[0] = 1:把 $s[0]$ 预先放进桶,对应"从数组头开始的合法区间"。- 遍历每个前缀和余数:
ans加上桶里同余数的数量;然后把当前余数入桶。
配对原理:若余数 $r$ 已经出现 $m$ 次,则当前这个前缀和可以和前面 $m$ 个前缀和配成 $m$ 个新区间。
五、版本 2:单循环压缩(竞赛推荐)
其实不需要保存全部前缀和——只用一个变量 sum 持续累加,省掉 $O(N)$ 内存。
1 |
|
压缩版要点
- 删掉了存储全部前缀和的
s数组,只用sum保存当前前缀和; - 数学上和版本 1 完全等价,只是把"先求全部前缀和,再统计"合并成"边读边做";
- 内存从 $O(N)$ 降到 $O(K)$,代码更短。
⚠️ 必看的坑
| 坑 | 说明 |
|---|---|
ans 必须 long long |
区间数量可能很大,int 必溢出 |
cnt 也建议 long long |
防止计数溢出 |
cnt 大小是 $K$ |
$0 \le \text{rem} < K$ |
rem = sum % k 在 C++ 里负数取模可能为负 |
题目保证 $A_i > 0$,sum 一直正,没问题 |
六、样例手算
输入:
1 | 5 2 |
初始:sum = 0, ans = 0, cnt[0] = 1, cnt[1] = 0
| i | x | sum | rem | ans += cnt[rem] | cnt[rem]++ |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1 | ans += 0 = 0 | cnt[1]=1 |
| 2 | 2 | 3 | 1 | ans += 1 = 1 | cnt[1]=2 |
| 3 | 3 | 6 | 0 | ans += 1 = 2 | cnt[0]=2 |
| 4 | 4 | 10 | 0 | ans += 2 = 4 | cnt[0]=3 |
| 5 | 5 | 15 | 1 | ans += 2 = 6 | cnt[1]=3 |
ans = 6 ✔
七、复杂度对比
| 解法 | 时间 | 空间 |
|---|---|---|
| 暴力枚举 $l, r$ | $O(N^2)$(超时) | $O(1)$ |
| 前缀和 + 桶(版本 1) | $O(N)$ | $O(N + K)$ |
| 前缀和 + 桶(版本 2,压缩) | $O(N)$ | $O(K)$ |
八、易错点清单
ans用int:$N = 10^5$ 时区间数可达 $\sim 10^{10}$,int溢出 → 必须long long。- 忘
cnt[0] = 1:漏掉就漏掉所有从数组头开始的合法区间。 - 负数取模:
sum % k在sum可能为负时得到负数,C++ 里要((sum % k) + k) % k。本题 $A_i > 0$,不用处理。 cnt大小开错:必须是 $K$,不是 $N$。- 写成 $O(N^2)$ 暴力:必超时。
九、小结
- 核心:区间和是 $K$ 倍数 $\iff$ 两端前缀和对 $K$ 同余;
- 算法:前缀和 + 同余桶计数,时间 $O(N)$;
- 先用版本 1 写明白,再压成版本 2——比赛里直接写版本 2 即可;
- 口诀:前缀和同余,区间 K 倍数;桶计余数次,边读边统计。