K倍区间

K倍区间

题目描述

C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210318220111391.png


核心思路

1.最简单的方法,采用暴力枚举,三层循环

for(int r = 1; r <= n; r++)
    for(int l = 1; l <= n; l++)
    {
        int sum = 0;
        for(int i = l; i <= r; i++)
        {
            ·········计算区间和
        }
        判断是否可整除k
    }
  1. 升级版1:优化最里面一层循环,将其优化,可以另外开辟一个数组sum,存前n项和,这样,里面可以变成sum[r]-sum[l-1]

    for(int r = 1; r <= n; r++)
        for(int l = 1; l <= n; l++)
        {
            if((sum[r]-sum[l])%k==0)
            {
    
            }
        }
    

3.极度优化

由于是求某一段连续的子序列的总和,因此容易想到先用前缀和,来求出区间[1,n]中的前缀和。是K的倍数,说明%k为0。我们可以枚举右端点,从1枚举到N,当枚举到某个右端点R时,固定右端点,那么题目想问的就是区间[1,R]中有多少个L,使得能够满足 ( S [ R ] − S [ L − 1 ] ) % K = 0 (S[R]-S[L-1])\%K=0 (S[R]S[L1])%K=0,让区间都减去1,即区间为[0,R-1],那么问题就是:区间[0,R-1]中有多少个L,使得能够满足 ( S [ R ] − S [ L ] ) % K = 0 (S[R]-S[L])\%K=0 (S[R]S[L])%K=0。由模运算的性质: ( a − b ) % k = ( a % k − b % k ) % k (a-b)\%k=(a\%k-b\%k)\%k (ab)%k=(a%kb%k)%k,因此有 ( S [ R ] % K − S [ L ] % K ) % K = 0 (S[R]\%K-S[L]\%K)\%K=0 (S[R]%KS[L]%K)%K=0,即有 S [ R ] % K = S [ L ] % K S[R]\%K=S[L]\%K S[R]%K=S[L]%K。那么问题就转化为:有多少个 S [ L ] S[L] S[L],使得 S [ L ] % K S[L]\%K S[L]%K的余数与 S [ R ] % K S[R]\%K S[R]%K的余数相等。

我们开一个数组cnt,其中cnt[x]表示模K余数为x的数的个数

问题:如何理解cnt[0]=1呢?

这里是用前缀和数组来算某个区间的和,前缀和数组s[0] = 0, s[i] = a[1] + a[2] + … + a[i],如果想算a[L] + a[L + 1] + … + a[R],它的值就等于s[R] - S[L - 1],所以当L = 1的时候,就会用到s[0]了,s[0]存的是模K余数为0的数的个数,由于s[0] = 0,所以最初等于0的有1个数,所以cnt[0] = 1。

问题:如何理解下面这段代码呢?

    for (int i = 1; i <= n; i ++ )
    {
        res += cnt[s[i] % k];
        cnt[s[i] % k] ++ ;
    }

这里i是右端点,然后求有多少个左端点,使得区间内的数是k的倍数,1 ~ i的前缀和的余数是s[i] % k,所以满足要求的左端点的数量就是余数相同的前缀和数量,就是cnt[s[i] % k]。cnt[sum[r] % k] 表示余数是sum[r] % k 有多少个,亦即余数是sum[l] % k 的l有多少个(这里的r其实就是i)。


代码

#include<iostream>
using namespace std;
typedef long long LL;
const int N=100010;
int n,k;
LL s[N];    //前缀和
LL cnt[N];  //cnt[i]存储的是模k余数为i的数的个数
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&s[i]);
        s[i]+=s[i-1];
    }
    LL ans=0;
    cnt[0]=1;//区间[0,0]中余数为0的数的个数有1个,就是0这个数字。
    for(int i=1;i<=n;i++)//枚举右端点r
    {
        //就是说枚举到当前点i时,s[i]%k求出的是余数x,那么i之前可能已经出现过余数为x
        //那么当枚举到当前右端点i时,cnt[x]就是区间[1,i-1]中出现过余数为x的数的个数
        ans+=cnt[s[i]%k];	//拿出当前余数相同时,当前值之前余数的个数
        //余数为x的数的个数在端点i出现了一次,则要加上
        cnt[s[i]%k]++;//记录模k余s[i]%k的数的个数
    }
    printf("%lld\n",ans);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值