2022/04/26学习

今天主要写了几道有关前缀和的题目,其中有一道我觉得挺好的,分享一下。

  • 第一题: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

这题刚开始我只想到了先用前缀和去计算,然后用双重循环去遍历一遍,但是最后的复杂度是10^10,而题目的要求是在10^8之内,也就是只能O(N)或者O(nlogn),所以第一遍毫无疑问时间超限了。接下来就是看在这两层循环之间优化,看能不能变成一层循环。

后面去看了acwing里面的视频,发现可以有一个非常巧妙的方法(用空间换时间),可以让时间复杂度变为O(N).

步骤:

  1. 刚开始还是按照原来的用前缀和去计算
  2. 我们本来是去找左区间和右区间去相减,现在我们只遍历右区间,那么我们可以去看这个右区间位置的前缀和被k取余是多少,而前面如果有余数相等的,就加上多少个余数相等的个数(因为右区间-左区间,如果余数相等的话,相减就是k的倍数
  3. 所以我们要再建立一个数组(空间换时间),去再遍历一遍,边算个数边算余数

看不懂我的意思没关系,举个例子画个图你就会明白了 ,就拿本题的样例来举例子吧 

 余数一样时候,右区间减你那个余数一样的那个位置的值,相减之后余数就没了,就是k的倍数。

而余数为0时,就是cnt的下标为0时,因为本身就是k的倍数,所以初始的时候应该是1.

下面给出ac代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define LL long long
#define N 100010
int n,k;
LL h[N],cnt[N]; 

int main()
{
    scanf("%d%d",&n,&k);
    for(int i = 1;i<=n;i++)
    {
        scanf("%lld",&h[i]);
        h[i]+=h[i-1]; //前缀和
    }
    LL ans = 0;
    cnt[0] = 1; //因为余数为0本身就有一个解
    for(int i = 1;i<=n;i++)
    {
        //因为前面余数一样的,相减之后那个余数就被减没了,就是k的倍数了
        //所以要计算前面有多少的某个余数的个数
        ans+=cnt[h[i]%k]; 
        cnt[h[i]%k]++;  //该值余数个数++
    }
    printf("%lld\n",ans); //输出
    return 0;
}

一定要注意,这里的数据范围很大,所以结果必须要用long long,不然会爆,还有建议用scanf和printf,因为scanf和printf的速度比cin和cout快。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

烂尾歌·

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

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

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

打赏作者

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

抵扣说明:

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

余额充值