Bootstrap

k倍区间(前缀和)

 

#include <iostream>
using namespace std;
int main()
{
  // 请在此输入您的代码
  int n,k;
  cin>>n>>k;
  int a[100005];
  for(int i=0;i<n;i++){
    cin>>a[i];
  }
  long long ans=0;
  long long s = 0;
  int cnt[100005]={0};
  cnt[0]=1;
  
  for(int i=0;i<n;i++){
      s+=a[i];
      int mod=s%k;
      ans+=cnt[mod];
      cnt[mod]++;
  }
  cout<<ans;

  return 0;
}

 

cnt[100005] 是一个数组,用来记录前缀和模 k 的结果出现的次数。

为什么 cnt[0] = 1?这是因为当 s % k == 0 时,表示从数组的开头开始,到当前位置 i 的子数组和已经是 k 的倍数。

  • s += a[i]; 计算当前前缀和,s 是从数组的第一个元素到第 i 元素的和。

  • mod = s % k; 计算当前前缀和模 k 的值,即 s 除以 k 的余数。

如果某个前缀和的模 k 值已经出现过,那么说明从某个之前的点到当前位置的子数组和是 k 的倍数。

核心思路是利用前缀和的性质,通过模运算来高效地判断子数组是否为 k 的倍数。通过数组 cnt 来记录每个模 k 的前缀和出现的次数,每当相同的模值出现时,说明存在一个符合条件的子数

实例:

5 3
1
2
3
4
5

第一次循环 (i = 0):
  • s += a[0] = 1,当前前缀和为 1。
  • mod = 1 % 3 = 1,计算当前前缀和的模值。
  • ans += cnt[mod] = ans + cnt[1] = 0 + 0 = 0,由于 cnt[1] 为 0,表示之前没有遇到过前缀和模值为 1 的情况。
  • cnt[1]++,更新 cnt[1],现在 cnt[1] = 1
第二次循环 (i = 1):
  • s += a[1] = 1 + 2 = 3,当前前缀和为 3。
  • mod = 3 % 3 = 0,计算当前前缀和的模值。
  • ans += cnt[mod] = ans + cnt[0] = 0 + 1 = 1,此时 cnt[0] = 1,说明之前有一个前缀和模值为 0 的子数组(即从数组开头到当前元素的子数组和已经是 3 的倍数),所以增加 1 个符合条件的子数组。
  • cnt[0]++,更新 cnt[0],现在 cnt[0] = 2
第三次循环 (i = 2):
  • s += a[2] = 3 + 3 = 6,当前前缀和为 6。
  • mod = 6 % 3 = 0,计算当前前缀和的模值。
  • ans += cnt[mod] = ans + cnt[0] = 1 + 2 = 3,此时 cnt[0] = 2,表示之前有两个前缀和模值为 0 的子数组,因此可以增加 2 个符合条件的子数组。
  • cnt[0]++,更新 cnt[0],现在 cnt[0] = 3
第四次循环 (i = 3):
  • s += a[3] = 6 + 4 = 10,当前前缀和为 10。
  • mod = 10 % 3 = 1,计算当前前缀和的模值。
  • ans += cnt[mod] = ans + cnt[1] = 3 + 1 = 4,此时 cnt[1] = 1,表示之前有一个前缀和模值为 1 的子数组,因此可以增加 1 个符合条件的子数组。
  • cnt[1]++,更新 cnt[1],现在 cnt[1] = 2
第五次循环 (i = 4):
  • s += a[4] = 10 + 5 = 15,当前前缀和为 15。
  • mod = 15 % 3 = 0,计算当前前缀和的模值。
  • ans += cnt[mod] = ans + cnt[0] = 4 + 3 = 7,此时 cnt[0] = 3,表示之前有三个前缀和模值为 0 的子数组,因此可以增加 3 个符合条件的子数组。
  • cnt[0]++,更新 cnt[0],现在 cnt[0] = 4

最终结果:

  • 输出结果 ans = 7,即符合条件的子数组总共有 7 个。
;