相关题目:
题目描述
给定一个长度为 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
思路
1.仔细读题不难发现,这就是一维前缀和问题:若给定数组L到R为的和为K的倍数,那么答案anscount++。
(如果对前缀和没啥概念,我找到了一篇前缀和讲的很好的博客,网站放这里了DWVictor——前缀和 DWVictor大佬写的,很详细,如果这样侵权了请告诉我,我删,才开始写博客有些规矩不是很清楚,有什么不对的希望大家批评指正。)
这题数据很大,硬算肯定超时,而前缀和正好是降低时间复杂度度的。
2.L到R为的和用前缀和的表示方法是sum[R]-sum[L-1],草稿纸上画一下,就能大概推出来
3.有了以上的方法做指导,我们接下来做的就很容易了,只需要找出有多少个sum[R]-sum[L-1]%K==0
就行,很容易想到的一个方法双重循环
for(int i=1;i<=N;i++)
for(int j=1;j<=i;j++){
if((s[i]-s[j-1])%K==0&&s[i]-s[j-1]>0){
count++;
}
}
但是这样时间依然会超,只能拿到部分分,原因是N<=100000
,最坏的情况双重循环得执行1的10次方次,大于一个亿,所以还得优化。
4.双重循环超时,我们就得考虑一重循环,优化掉一个循环,要想优化掉一个就必须从判断条件入手,因为(sum[R]-sum[L-1])%K==0
,是双重循环中最重要的代码 ,我们可以将公式变形
(sum[R]-sum[L-1])%K==0
->sum[R]%K==sum[L-1]%K
,从这个公式我们可以得出:对前缀和取模之后,结果相等两个前缀和就能组成一个k倍区间,意思就是说对于给定K,如果有任意两个前缀和%K,的结果相等,这时的L和R就能构成一个k倍区间;
5.我们用count数组来存放取摸后的结果,count[i]表示取摸后值为i
的区间个数,我们要找的就是另一个值为
i的前缀和
,如果有另一个anscount++;这样就能把双重变成一重,1—N,对每一个前缀和sum[i]
取摸,有相同结果的,anscount++;
6.还有一点在循环之前sum[0]%k
也等于0,因此余数为0的一个前缀和已经有一个了,所以count[0]=1;
然后就是全部代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int N=input.nextInt();
int K=input.nextInt();
int count[]=new int [1000001];
int arr[]=new int[N+1];
int s[]=new int[N+1];
for(int i=1;i<=N;i++){
arr[i]=input.nextInt();
s[i]=s[i-1]+arr[i]; //计算前缀和
s[i]%=K; //在这里直接对前缀和取摸
}
count[0]=1;
long anscount=0; //int 会爆掉
for(int i=1;i<=N;i++)
{
anscount+=count[s[i]];//除了count[0]=1,其余的初始值都为0,
count[s[i]]++; //如果出现过一次后加1,出现过两次及以上,anscount++,
//我们就是要找总共有多少对,
//只出现一次虽然count[i]加1但总的答案不会加
}
System.out.println(anscount);
}
}
最后还是感谢y总的指导