AcWing 1230. K倍区间

题目

给定一个长度为 N的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj
之和是 K 的倍数,我们就称这个区间 [i,j]是 K倍区间。

你能求出数列中总共有多少个 K 倍区间吗?

输入格式

第一行包含两个整数 N 和 K。
以下 N行每行包含一个整数 Ai。

输出格式

输出一个整数,代表 K 倍区间的数目。

数据范围

1 ≤ N,K ≤ 100000,
1 ≤ Ai ≤ 100000

输入样例:

5 2
1
2
3
4
5

输出样例:

6

要点1: 判断k倍区间

首先我们都知道需要计算前缀和才能在O(1)的情况下得到区间和,然后判断区间和模上k是否等于0得到区间否是是k的倍数。

y总用了一个更加巧妙的办法,即只要前缀和区间头尾两个数的模相同,那么这个区间就是k倍区间。推导:(s[r] - s[l-1])%k -> s[r]%k == s[l-1]%k

要点2:cnt数组

cnt数组其实是一个动态的数组,存的是第1i-1的数在枚举到第i个数时的模为0k-1的个数情况,即cnt[x] = num (x模数,num个数)。

那为什么要用这种动态数组,因为我们的本意是在1~i-1中找到和i模数相同数的个数,正常情况要找这个个数我们一般会想到用俩重循环,但是这是非常浪费的,所以我们开一个数组cnt来存个数,一边走一边加,这样就可以一直保证当前的cnt数组就是枚举到当前第i个数时特有的一个数组

要点3:cnt[0]的初始化

要知道cnt[0]初始化为1的原因就一定要理解cnt这个数组到底是干嘛的,再再啰嗦一遍,cnt数组就是存模为x的数的个数,这个是cnt数组的含义。

但是cnt数组还要保证res的累加,res是根据当前cnt数组存的个数计数的,那么我们想想第i个数模是0,那么它除了和前i个数组成n个k倍区间,是不是自己也单独是一个k倍区间,所以所有模为0的数的真正的区间个数都是n+1,所以直接给cnt[0]初始化为1,那么所有模为0的数在累加res时都会自动+1

既然我们已经理解初始化的原因就是因为模为0的数自己也是一个区间要加1,所以可以改写成如下代码:

    LL res=0;
    //cnt[0]=1; //不进行初始化
    for(int i=1;i<=n;i++)
    {
        res+=cnt[s[i]%k];
        if(s[i]%k==0) res++; //直接对模0的数特判res+1即可
        cnt[s[i]%k]++;
    }

代码:

#include<iostream>
#include<cstdio>

using namespace std;

typedef long long int LL;

const int N=100010;
LL cnt[N],s[N];

int main()
{
    int n,k;
    cin>>n>>k;
    
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&s[i]);
        s[i]+=s[i-1];
    }
    
    LL res=0;
    cnt[0]=1;
    for(int i=1;i<=n;i++)
    {
        res+=cnt[s[i]%k];
        cnt[s[i]%k]++;
    }
    cout<<res;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值