每日一题——k倍区间(前缀和)

文章讨论了如何利用前缀和和模k同余性快速找到k倍区间,降低时间复杂度至线性。
摘要由CSDN通过智能技术生成

2、k倍区间

给定一个长度为 𝑁 的数列,𝐴1,𝐴2,…𝐴𝑁,如果其中一段连续的子序列 𝐴𝑖,𝐴𝑖+1,…𝐴𝑗 之和是 𝐾 的倍数,我们就称这个区间 [𝑖,𝑗] 是 𝐾 倍区间。

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

输入格式

第一行包含两个整数 𝑁 和 𝐾。

以下 𝑁 行每行包含一个整数 𝐴𝑖。

输出格式

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

数据范围

1≤𝑁,𝐾≤100000,
1≤𝐴𝑖≤100000

输入样例:
5 2
1
2
3
4
5
输出样例:
6
第一遍自己写的代码:
#include <iostream>
using namespace std;

bool compute(int *x, int k) {
    if (*x % k == 0) return true;
    else return false;
}

int main() {
    int n, k, count = 0;
    cin >> n >> k;
    int a[n];
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    
    for (int j = 0; j < n; j++) {
        int *sum = new int(0);
        for (int t = j; t < n; t++) {
            *sum += a[t];
            if (compute(sum, k)) {
                count += 1;
            }
        }
        delete sum;
    }
    
    cout << count;
    
    return 0;
}

 我自己写代码就很喜欢按照题目给出的求解要求顺序分析,而且数据观念不强,只局限于给出的输入样例分析写代码,不能想到数据范围是十万级的,导致写出来的代码虽然能通过测试,但是时/空复杂度高。

学习后改进代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

typedef long long LL;
const int N=100010;

int n,k;
LL s[N];
int cnt[N];

int main() {
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		//input an array of length N
		scanf("%d",&s[i]);
		//Calculate the sum of prefixes
		s[i]+=s[i-1];
	}
	LL res=0;
	//An array of remaining value kinds
	cnt[0]++;
	for(int i=1;i<=n;i++)
	{
		//difficulty
		res+=cnt[s[i]%k];
		cnt[s[i]%k]++;
	}
    printf("%11d\n",res);
    return 0;
}

这道k倍区间的题,我觉得最难的点在于你怎么去利用题目中给的“和为k的倍数”这个条件,前缀和的利用都是十分简单的。

前面我们不用说了,s[i]+=s[i-1]就是在计算前缀和,然后我们主要来分析一下后半段代码的思想和用到的原理。

LL res=0;
//An array of remaining value kinds
cnt[0]++;
for(int i=1;i<=n;i++)
{
	//difficulty
	res+=cnt[s[i]%k];
	cnt[s[i]%k]++;
}

将数列A的前缀和记为Si,那区间值就可以用Si-Sj来计算,又因为这段区间(一段连续的子序列之和)是k的倍数,所以这段区间都是模k余0,即是Si-Sj≡0,意味着Si和Sj是模k同余的(这里用到了数学的知识,找了一段简单的模k同余的说明)。而cnt[0]首先设为1,是因为当算出有一个s[i]模k余0,则其对应的区间即为一个k倍区间。

 用模k同余的方式去计算k倍区间的个数就极大降低了时间复杂度,按我原本的方法,时间复杂度就为O(n²),如果只用计算s[i]%k,则时间复杂度为O(n),这就是10⁵~10¹⁰级别的区别。所以难的不是代码,而是算法优化。今天的我仍然没有算法思想,明天的我继续努力🫡🫡

看完上述代码和思路分析,还是不清楚的,请自己上网搜索讲解视频,这里就不转载了。

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值