原题链接
题目难度:中等
题目来源:第八届蓝桥杯省赛C++ B组,第八届蓝桥杯省赛Java B/C组
题目描述
给定一个长度为 NNN 的数列,$A_1, A_2, … A_N$,如果其中一段连续的子序列 $A_i, A_{i+1}, … A_j$ 之和是 K 的倍数,我们就称这个区间 $[i, j]$ 是 K 倍区间。
你能求出数列中总共有多少个 K 倍区间吗?
输入格式
第一行包含两个整数 N 和 K。
以下 NNN 行每行包含一个整数 $A_i$。
输出格式
输出一个整数,代表 K 倍区间的数目。
数据范围
$1 \le N, K \le 100000$,
$1 \le A_i \le 100000$
输入样例:
1 | 5 2 |
输出样例:
1 | 6 |
题目分析
题目意思十分清楚,就是给一个静态数组,问其中有多少个连续子数组的和是K的倍数
这里我们观察数据范围,大概采用的时间复杂度要小于$O(n\log n)$
我们如果用最暴力的做法,枚举所有方案,对于枚举来说最重要的就是如何能够不重不漏的枚举出所有方案
对于这个题而言,就是枚举左端点和右端点,这样就可以枚举所有的方案了,然后再判断左右端点之间的和是否是k的倍数,这里的时间复杂度是$O(n^2)$
这里我们想办法找到优化方法,假设先枚举右端点,再枚举左端点,那么对于每一个右端点,他的目的就是要找到有若干左端点,满足K倍条件
这里我们加入前缀和的思想
K倍条件为$(pre[R]-pre[L])%K == 0$
经过变换其实可以变成$pre[R]%K==pre[L]%K$
那么问题就变成了,有多少个$pre[R]$和$pre[L]$的余数相同
那我们其实就可以开一个数组,用于存储在前缀和数组中除K的每个余数的个数
在遍历前缀和数组的过程中,每一次遇到目标的余数,就在答案上加上之前的个数,因为新的数字和之前的每一个数字都算一组新的满足条件的情况,因此只需要$O(n)$的时间
示例代码
1 |
|
对于cnt[0] = 1的理解
对于余数非0的数而言,他的结果一定是从0加到总数减1,对于余数是0本身的数,他自身就可以作为一个结果,因此要初始化为1
这里简单举个例子
例如
k = 2
序列为:1,2,3,4,5
前缀和为:1,3,6,10,15
余数为0的:6,10 一共两个
余数为1的:1,3,15 一共三个
我们来模拟一下这个过程
假设从余数为0的时候开始,没有初始化cnt[0] = 1
走到6,ans += 0
走到10,ans += 1
这里的含义就是,从6到10算一种情况,因此余数为0的情况就只有一种
如果余数为1
走到1,ans += 0
走到3,ans += 1
走到15,ans += 2
这里的含义就是走到1,只有一个数,不算区间,不算一次,走到3,1和3算一次,记录,走到15,1和15算一次,3和15算一次,因此加2
所以如果不初始化的话,结果应该是4
这里我们会发现一个问题,如果余数是0的话,他本身也可以作为一个区间
这时我们回头,初始化cnt[0] = 1
走到6,ans += 1
走到10,ans += 2
这里的含义就是,6本身是一种,加1,10本身是一种,6到10是一种,加2,一共就是3种
再加上余数是1的3种
答案就是6,这就是正确答案了