题目分析
题目要求找出数列中所有子区间的和是K的倍数的区间数量。直接暴力枚举所有区间的时间复杂度为O(N²),对于N=1e5的数据会超时。必须采用数学优化思路。
核心思路:同余定理+前缀和
数学原理
- 前缀和数组:设
sum[i]
表示前i项的和,则区间和a[i..j] = sum[j] - sum[i-1]
。 - 同余定理:若两个前缀和满足
sum[j] % K == sum[i-1] % K
,则区间[i,j]
的和必为K的倍数(因为sum[j]-sum[i-1]
是K的倍数)。
实现步骤
- 计算前缀和模K的余数:遍历时计算
sum[i] % K
。 - 统计余数出现次数:维护数组
cnt[]
,cnt[r]
表示余数r出现的次数。 - 累加组合数:当余数r再次出现时,当前前缀可与之前所有余数为r的前缀形成有效区间。
关键边界处理
- 初始化
cnt[0]=1
:考虑前缀和自身是K倍数的情况(如sum[3]%K=0
,说明区间[1,3]
是解,此时需要cnt[0]
已有初始值才能被统计到)。
代码细节说明
java
复制代码
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), k = sc.nextInt();
long[] cnt = new long[k]; // 余数计数器
cnt[0] = 1; // 关键初始化
long sum = 0, ans = 0;
for (int i = 0; i < n; i++) {
sum += sc.nextInt();
int r = (int)(sum % k); // 当前前缀和的余数
if (r < 0) r += k; // 处理负数余数
ans += cnt[r]; // 累加相同余数的出现次数
cnt[r]++; // 更新计数器
}
System.out.println(ans);
}
}
示例详解(输入样例)
输入:
复制代码
5 2
1 2 3 4 5
处理过程:
步骤 | 元素 | 前缀和 | sum%2 | 统计操作 | cnt变化 | 新增答案 |
---|---|---|---|---|---|---|
0 | - | 0 | 0 | ans+=1 | [1,0] | +1 |
1 | 1 | 1 | 1 | ans+=0 | [1,1] | 0 |
2 | 2 | 3 | 1 | ans+=1 | [1,2] | +1 |
3 | 3 | 6 | 0 | ans+=1 | [2,2] | +1 |
4 | 4 | 10 | 0 | ans+=2 | [3,2] | +2 |
5 | 5 | 15 | 1 | ans+=2 | [3,3] | +1 |
总答案:1+1+1+2+1=6,与样例输出一致。
复杂度分析
- 时间复杂度:O(N),仅需一次遍历。
- 空间复杂度:O(K),计数器数组大小与K相关。