[蓝桥杯 2017 省 B] k 倍区间
一、题目
题目描述
给定一个长度为 N N N 的数列, A 1 , A 2 , ⋯ A N A_1,A_2, \cdots A_N A1,A2,⋯AN,如果其中一段连续的子序列 A i , A i + 1 , ⋯ A j ( i ≤ j ) A_i,A_{i+1}, \cdots A_j(i \le j) Ai,Ai+1,⋯Aj(i≤j) 之和是 K K K 的倍数,我们就称这个区间 [ i , j ] [i,j] [i,j] 是 K K K 倍区间。
你能求出数列中总共有多少个 K K K 倍区间吗?
输入格式
第一行包含两个整数 N N N 和 K K K ( 1 ≤ N , K ≤ 1 0 5 ) (1 \le N,K \le 10^5) (1≤N,K≤105)。
以下 N N N 行每行包含一个整数 A i A_i Ai ( 1 ≤ A i ≤ 1 0 5 ) (1 \le A_i \le 10^5) (1≤Ai≤105)。
输出格式
输出一个整数,代表 K K K 倍区间的数目。
样例 #1
样例输入 #1
5 2
1
2
3
4
5
样例输出 #1
6
提示
时限 2 秒, 256M。蓝桥杯 2017 年第八届
二、分析题目
1、通过题目可知
我们需要计算很多个区间内的值的和,通过这些区间内的和与k取模运算,判断这些区间是否符合条件。显然,这是一道前缀和题目,前缀和可以很轻松的计算一个区间内的值的和。
关于前缀和的知识,传送门:https://blog.csdn.net/m0_73096566/article/details/129129517
2、数学知识
解题还需要另外一个数学知识:同余运算,具体公式如下:
A%k=B%k,那么|A-B|%k=0。
3、结合1和2来看:
(1)根据同余运算公式和本题的关系:题目中所需要的是区间和整除,也就是这个公式的后半部分(|A-B|%k=0)。那么我们就需要想方设法的找到,两个有着相同余数的A和B。
(2)根据前缀和的知识,我们如果想知道一个区间(i,j)的区间和,只需要用S(j)-S(i-1)即可,正好完美符合3(1)中的A和B的定义。
(3)也就是说:我们需要在前缀和S(1)到S(n)中,去统计有着相同余数的S(i)个数:same_mod_cnt个,然后在same_mod_cnt中,选择两个即可组成一对A和B,那么只需要计算
C
same
_
mod
_
cnt
2
\text{C}_{\text{same }\!\!\_\!\!\text{ mod }\!\!\_\!\!\text{ cnt}}^{2}
Csame _ mod _ cnt2即可。
4、综上,大致思路为:
(1)计算所有元素的前缀和,sum[i]表示。
(2)对每一个前缀和sum[i]取余运算,计算每一种余数所对应的前缀和个数,same_mod_cnt[j]表示余数为j的前缀和个数。
(3)for循环same_mod_cnt数组,累加统计
C
same
_
mod
_
cnt[j]
2
\text{C}_{\text{same }\!\!\_\!\!\text{ mod }\!\!\_\!\!\text{ cnt[j]}}^{2}
Csame _ mod _ cnt[j]2的值。
(4)有一个点值得注意一下,就是余数为0的时候,余数为0的时候,不是
C
same
_
mod
_
cnt[j]
2
\text{C}_{\text{same }\!\!\_\!\!\text{ mod }\!\!\_\!\!\text{ cnt[j]}}^{2}
Csame _ mod _ cnt[j]2,而应该是
C
same
_
mod
_
cnt[j]
2
\text{C}_{\text{same }\!\!\_\!\!\text{ mod }\!\!\_\!\!\text{ cnt[j]}}^{2}
Csame _ mod _ cnt[j]2+same_mod_cnt[j],因为区间也可以只有一个数,此时每一个余数为0的前缀和都满足条件,都可以被整除,所以在j=0的时候(余数为0),要做一个特殊处理。
事实上:
C
n
2
+
n
\text{C}_{\text{n}}^{2}+\text{n}
Cn2+n
= n ! 2 ! ( n − 2 ) ! + n \frac{n!}{2!\left( n-2 \right)!}+\text{n} 2!(n−2)!n!+n
= n ( n − 1 ) 2 + n \frac{n(n-1)}{2}+\text{n} 2n(n−1)+n
= n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)
=
C
n+1
2
\text{C}_{\text{n+1}}^{2}
Cn+12
所以,在统计j=0的时候,只需要比平常多统计1即可。
5、举例
样例输入 #1
3 5
1
5
9
样例输出 #1
2
分析
n=3,k=5;
nums[]:1,5,9;
sum[]:1,6,15;
same_mod_cnt[]:1,2,0,0,0;//正常情况
//但是由于4(4)的说明,所以应为:
same_mod_cnt[]:2,2,0,0,0;
for循环same_mod_cnt[]数组:
累加
C
n
2
\text{C}_{\text{n}}^{2}
Cn2:
C
2
2
+
C
2
2
\text{C}_{\text{2}}^{2}+\text{C}_{\text{2}}^{2}
C22+C22=2
6、int超时
注意int会超时,设置为long long
三、代码
#include<iostream>
#include <vector>
using namespace std;
int main()
{
//输入数据
long long n,k;cin>>n>>k;
vector<long long>mod_array(k,0);
mod_array[0]=1;
//事实上不用存储每个前缀和的值,因为每个前缀和在求余后就不会再使用了
long long sum=0;
for(long long i=0;i<n;i++)
{
long long num;cin>>num;
sum+=(num%k);
mod_array[sum%k]++;
sum%=k;
}
long long cnt=0;
for(long long i=0;i<k;i++)
{
cnt+=(mod_array[i]*(mod_array[i]-1))/2;
}
cout<<cnt<<endl;
return 0;
}