今天主要写了几道有关前缀和的题目,其中有一道我觉得挺好的,分享一下。
- 第一题: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).
步骤:
- 刚开始还是按照原来的用前缀和去计算
- 我们本来是去找左区间和右区间去相减,现在我们只遍历右区间,那么我们可以去看这个右区间位置的前缀和被k取余是多少,而前面如果有余数相等的,就加上多少个余数相等的个数(因为右区间-左区间,如果余数相等的话,相减就是k的倍数)
- 所以我们要再建立一个数组(空间换时间),去再遍历一遍,边算个数边算余数
看不懂我的意思没关系,举个例子画个图你就会明白了 ,就拿本题的样例来举例子吧
余数一样时候,右区间减你那个余数一样的那个位置的值,相减之后余数就没了,就是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快。