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
首先考虑暴力算法,枚举所有的区间,并统计符合要求的区间数量
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int q[N];
int cnt;
int main()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++)
scanf("%d", &q[i]);
/*暴力做法*/
for (int i = 1; i <= n; i++) //i为区间的右端点
{
for (int j = 1; j <= i; j++) //j为区间的左端点
{
int sum = 0;
for (int y = j; y <= i; y++)
{
sum += q[y];
}
if (sum % k == 0)
cnt++;
}
}
cout << cnt;
}
很显然,上面的做法会超时,只能得到少部分分数,需要进行优化
在上面的算法中,使用到了区间的和,所以考虑使用前缀和进行优化
在使用前缀和的过程中有两个需要注意的点
1.需要注意 s[n]=s[n-1]+q[n]时可能出现数组越界,故要从1开始进行循环
2.前缀和数组可能很大以至于爆int,故使用long long 进行存储更为稳妥
代码如下
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int q[N];
int s[N];
int cnt;
int main()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++)
scanf("%d", &q[i]);
/*前缀和初始化*/
for (int i = 1; i <= n; i++)
s[i] = s[i - 1] + q[i];
for (int i = 1; i <= n; i++) //i为区间的右端点
{
for (int j = 1; j <= i; j++) //j为区间的左端点
{
/*使用前缀和进行优化*/
int sum = s[i] - s[j - 1];
if (sum % k == 0)
cnt++;
}
}
cout << cnt;
}
在使用前缀和进行优化之后,时间复杂度依然偏高,需要进一步优化
观察下面的这重循环
for (int j = 1; j <= i; j++) //j为区间的左端点
{
/*使用前缀和进行优化*/
int sum = s[i] - s[j - 1];
if (sum % k == 0)
cnt++;
}
其实质是在寻找 对于每个固定的右端点i,在s[0]到s[i-1]中,有多少个数与k的模与s[i]%k相等
所以可以考虑开一个数组cnt[N]用于存储s[0]到s[i-1]中,对应余数的数量
优化后的代码如下
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
long long q[N];
long long s[N];
long long cnt[N];
long long res;
int main()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++)
scanf("%lld", &q[i]);
/*前缀和初始化*/
for (int i = 1; i <= n; i++)
s[i] = s[i - 1] + q[i];
//由于s[0]%k==0,所以在循环之前cnt[0]已经有一个数s[0]了,所以将cnt[0]置为1
cnt[0] = 1;
for (int i = 1; i <= n; i++) //i为区间的右端点
{
/*这一重循环的作用即寻找区间s[0]到s[i-1]中有多少数除以k的余数与s[i]相同*/
res += cnt[s[i]%k];
cnt[s[i] % k]++;
/*
for (int j = 1; j <= i; j++) //j为区间的左端点
{
int sum = s[i] - s[j - 1];
if (sum % k == 0)
res++;
}
*/
}
cout << res;
}
上面的最后一次优化,脱离了解题的思路本身,直接使用了效果相同但是时间复杂度更低的代码进行替换。这种优化思路非常赞
另外,算法题往往有步骤分,所以不一定要追求满分,在大方向不出错的情况下,使用更多的优化方式进行优化,这样可以尽可能地提高单题的得分。