k倍区间(从暴力破解到利用两种数学思维的过程)
题目描述
输入描述
输出描述
输出一个整数,代表 K 倍区间的数目。
输入输出样例
示例
输入
5 2
1
2
3
4
5
输出
6
运行限制
- 最大运行时间:2s
- 最大运行内存: 256M
想法一:暴力求解
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,k;
cin >> n >> k;
int *a = new int[n];
int sum = 0;
for(int i=0;i<n;i++){
cin >> a[i];
}
for(int i=0;i<n;i++){
int b = a[i];
if(a[i]%k == 0){
++sum;
}
for(int j = i+1;j<n;j++){
b += a[j];
if(b%k == 0){
++sum;
}
}
}
cout << sum;
return 0;
}
- 只能通过两个样例,其余均超时,所以要考虑用空间换取时间。
想法二:前缀和
-
这是我第一次接触,类似于动态规划
-
前缀和是什么?
用一个简单的列子去介绍原数组: a[1], a[2], a[3], a[4], a[5], …, a[n] 前缀和: s[i] = a[1] + a[2] + a[3] + … + a[i] 前缀和就是用一个数组s去存数组a的前n项的和。 s[0] = 0 s[1] = a[1] s[2] = a[1] + a[2] s[n] = a[i] + a[2] + a[3] + …+a[n] 这样s[n]对应的就是a[1]—a[n]的和,s的每一项都这样对应,就构成了前缀和。 注:前缀和的下标一定要从1开始。 ———————————————— 版权声明:本文为CSDN博主「HaiFan.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_73888239/article/details/128313429
-
前缀和求区间大小
如何利用前缀和去求区间大小呢? 有一个公式:s[r] - s[l - 1]。 就是这个公式,他的时间复杂度O(1),这就要比暴力的做法快上很多了。
-
如何构成前缀和的形式?
for (int i = 1; i <= n; i++) { s[i] = s[i - 1] + a[i]; }
s[1] = s[0] + a[1]; s[2] = s[1] + a[2]; s[3] = s[2] + a[3]; s[n] = s[n - 1] + a[n] 去遍历a数组,把当前a[n]的数加上s[n-1]的数,就能得到s[n],这个s[n]就是a[1,n]的和。
-
代码实现
#include<bits/stdc++.h>
using namespace std;
int a[100010];
int s[100010];
int main(){
int n,k;
cin >> n >> k;
s[0] = 0;
for(int i=1;i<=n;i++){
cin >> a[i];
s[i] = s[i-1] + a[i];
}
long long ans = 0;
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
if((s[j]-s[i-1])%k == 0){
++ans;
}
}
}
cout << ans << endl;
return 0;
}
想法三:前缀和+差分
- 想法一和二没什么区别,时间复杂度相同。
#include<bits/stdc++.h>
using namespace std;
long a[100010];
long s[100010];
int main(){
int n,k;
cin >> n >> k;
for(int i=1;i<=n;i++){
cin >> a[i];
a[i]+=a[i-1];
s[a[i]%k]++;
}
long long ans = s[0];
for(int i = 0;i < k;++i){
ans += (s[i]*(s[i]-1))/2;
}
cout << ans << endl;
return 0;
}
- 先求出前i项和中属于k倍区间的数,加到s[0]中
- 其余不是k的倍数的区间,余数相同的两个区间相减得到的中间区间为k的倍数,将所有余数相同的区间两两相减,所得到的组合数为k的区间数
- 二者相减即可得出结果