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¹⁰级别的区别。所以难的不是代码,而是算法优化。今天的我仍然没有算法思想,明天的我继续努力🫡🫡
看完上述代码和思路分析,还是不清楚的,请自己上网搜索讲解视频,这里就不转载了。