k倍区间

5 阅读2分钟

题目分析

题目要求找出数列中所有子区间的和是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的倍数)。

实现步骤

  1. 计算前缀和模K的余数:遍历时计算sum[i] % K
  2. 统计余数出现次数:维护数组cnt[]cnt[r]表示余数r出现的次数。
  3. 累加组合数:当余数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-00ans+=1[1,0]+1
1111ans+=0[1,1]0
2231ans+=1[1,2]+1
3360ans+=1[2,2]+1
44100ans+=2[3,2]+2
55151ans+=2[3,3]+1

总答案:1+1+1+2+1=6,与样例输出一致。


复杂度分析

  • 时间复杂度:O(N),仅需一次遍历。
  • 空间复杂度:O(K),计数器数组大小与K相关。