# 前言
蓝桥杯2017省赛,编程题(C++)
一、题目描述
给定一个长度为 N的数列,A1, A2, ⋯AN,如果其中一段连续的子序列 Ai,Ai+1,⋯Aj (i≤j ) 之和是 K 的倍数,我们就称这个区间 [ i , j ] 是 K 倍区间。
你能求出数列中总共有多少个 K 倍区间吗?
输入:
第一行包含两个整数 N 和 K( 1≤N,K≤10的五次方 )
以下 N 行每行包含一个整数Ai ( 1≤Ai≤10的五次方)
输出描述
输出一个整数,代表 K 倍区间的数目。
示例输入:
5 2
1
2
3
4
5
示例输出:
6
运行限制
- 最大运行时间:2s
- 最大运行内存: 256M
二、思路
如果采用纯暴力搜索,需要三个for循环,必定会超时,那么可以考虑通过以下几步来进行优化,以减小时间复杂度。
-
首先,可以采用前缀和的思想,在输入Ai的过程中,就将前缀和求出,然后后续需要判断区间时,比如[i,j] 之间是否是k的倍数,我们就可以直接用A[j]-A[i-1]来求出i~j之间的累加和,单步操作的时间复杂度为O(1),相比于之前每次求和都需要的O(n),明显快出很多。
-
比较坑的一点,前缀和的数组一定要采用long long或更大的数据类型,int型是无法通过的
-
有了前缀和的优化,时间还是会超时,这个时候就要从整除即模K这个点去设法优化。按照题目的意思,对于(A[j]-A[i-1])%K == 0即为符合要求,这一步可以再分解为A[j] %K==A[i-1] %K,这样写出来,我们题目的条件也就变成了先要寻找有多少个在模K结果相同下的数据,然后再求出同样模K结果下这些数据的组合方式。
-
比如我们有5组相同的模K数据,那么对应的可能取值就为4+3+2+1+0,这里简单画一个图,就很容易理解
-
最后一个要注意的就是,模K等于0的情况,这个模K等于0,说明正好能整除K,那么是可以独立成组的,所以最后答案需要加上模K为0的个数
三、具体代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,k;
cin>>n>>k;
long long a[100030]={0};
long long b[100030]={0};
long long c[100030]={0}; //c中存放在该一余数下,共有多少个相同值
long long ans=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i]+=a[i-1];
b[i]=a[i]%k; //存放累加和后对k取余的结果
}
for(int i=1;i<=n;i++)
{
ans+=c[b[i]]; //这里实现了0+1+2+3+...这样的过程
c[b[i]]++; //统计同一余数下的个数
}
ans+=c[0]; //看看余数为0的个数,这些是可以独立成为一种情况
cout<<ans<<endl;
return 0;
}