蓝桥杯:K倍区间(两种方法)

一、问题描述
给定一个长度为N的数列,A1, A2, … AN,如果其中一段连续的子序列Ai, Ai+1, … Aj(i <= j)之和是K的倍数,我们就称这个区间[i, j]是K倍区间。
  你能求出数列中总共有多少个K倍区间吗?
输入格式
  第一行包含两个整数N和K。(1 <= N, K <= 100000)
  以下N行每行包含一个整数Ai。(1 <= Ai <= 100000)
输出格式
  输出一个整数,代表K倍区间的数目。
样例输入
5 2
1
2
3
4
5
样例输出
6
数据规模和约定
  峰值内存消耗(含虚拟机) < 256M
  CPU消耗 < 2000ms

二、解法一(推荐)
数学原理(组合数)+前缀和

1、数学原理
 有两个前缀和A(i 对应的),B(j 对应的),若A%k=B%k,那么区间 [ i , j ]的元素和一定是k的倍数,即K倍区间。

2、前缀和
 前缀和是化简有关数组第 i 项到第 j 项和的有效手段,可以大大降低程序的时间复杂度;
实现形式:sum=s[ j ]-s[ i-1 ]

3、解法思想
(1)首先,定义代码需要使用的数组,变量;
(2)接着,写关键函数judge(),在下面有函数详解
(3)最后,写main函数,调用judge()函数即可;

4、函数judge()详解

函数功能:实现K倍区间的判断和统计个数

函数执行过程
——》开始;
——》输入数组元素个数N,倍速K;
——》for循环:输入数组元素,计算前缀和、前缀和余数,对应余数数组增1;
——》for循环:计算组合数 C n 2 C_{n}^{2} ,累加求和;
——》返回K倍区间个数;
——》结束;

具体函数代码:

void judge(int n,int k)
{
	int temp;
	cin>>n>>k;
	sum[0]=0;              //给sum[0]赋初值,因为a[0]不赋值,默认是0;    
	reminder[0]=1;         //因为sum[0]%k==0恒成立,所以要统计进去; 
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];         //输入数组各元素;
		presum+=a[i]; 
		sum[i]+=presum;       //计算前缀和sum[i]; 
		temp=sum[i]%k;       //计算对应的前缀和的余数;   
		reminder[temp]++;    //将余数对应的数组值加1,实现统计个数功能; 
	}
	for(int i=0;i<k;i++)
	{
		count+=reminder[i]*(reminder[i]-1)/2;     //计算组合数,并累加; 
	}
	cout<<count;                   //返回K倍区间的个数; 
}

5、完整的解法代码:

#include<iostream>
using namespace std;

long long a[100000],sum[100000],reminder[100000];     //定义数组; 
long long presum=0,count=0;                          //定义计数因子,并赋初值0; 


void judge(int n,int k)
{
	int temp;
	cin>>n>>k;
	sum[0]=0;              //给sum[0]赋初值,因为a[0]不赋值,默认是0;    
	reminder[0]=1;         //因为sum[0]%k==0恒成立,所以要统计进去; 
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];         //输入数组各元素;
		presum+=a[i]; 
		sum[i]+=presum;       //计算前缀和sum[i]; 
		temp=sum[i]%k;       //计算对应的前缀和的余数;   
		reminder[temp]++;    //将余数对应的数组值加1,实现统计个数功能; 
	}
	for(int i=0;i<k;i++)
	{
		count+=reminder[i]*(reminder[i]-1)/2;     //计算组合数,并累加; 
	}
	cout<<count;                   //返回K倍区间的个数; 
}
int main()
{
	int N,K;
	judge(N,K);
	return 0;
}

运行截图:
在这里插入图片描述

运行环境:DEV c++

三、解法二
暴力枚举+前缀和

1、暴力枚举
 把所有的情况都执行一遍,注意判断该种情况是不是K倍区间。

2、前缀和
 在法一中介绍过了,这里就不在赘述了。

3、解法思想
(1)首先,定义代码需要使用的数组,变量;
(2)接着,写关键函数judge(),在下面有函数详解
(3)最后,写main函数,调用judge()函数即可;

4、函数judge()详解

函数功能:实现K倍区间的判断和统计个数

函数执行过程
——》开始;
——》输入数组元素个数N,倍速K;
——》for循环:输入数组元素,累加计算前 i 项和;
——》两层for循环:实现K倍区间的判断和统计个数;
——》返回K倍区间个数;
——》结束;

两层for循环功能分析
 外层for循环控制区间起点。
 内层for循环控制区间终点。
 枚举所有的区间,每个区间都用对应前缀和%K判断其是不是K倍区间。

具体函数代码

void judge(int n,int k)
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		presum+=a[i];
		sum[i]+=presum;                        //计算前i项和; 
	}
	sum[0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
		{
			if((sum[j]-sum[i-1])%k==0)      //计算区间[i,j]的和(利用前缀和),并判断是不是K倍区间; 
				count++;
		}
	}
	cout<<count;
}

5、完整解法代码

#include<iostream>
using namespace std;

long long a[100000],sum[100000];     //定义数组; 
long long presum=0,count=0;                          //定义计数因子,并赋初值0; 


void judge(int n,int k)
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		presum+=a[i];
		sum[i]+=presum;                        //计算前i项和; 
	}
	sum[0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
		{
			if((sum[j]-sum[i-1])%k==0)      //计算区间[i,j]的和(利用前缀和),并判断是不是K倍区间; 
				count++;
		}
	}
	cout<<count;
}

int main()
{
	int N,K;
	judge(N,K);
	return 0;
}

运行截图:
在这里插入图片描述

运行环境:DEV c++

四、法一和法二比较
对于法一:相较之于法二,法一的时间复杂度明显优于法二,执行起来占用的内存少,便于应用;但要有一定的数学基础,否则不易想到这样的算法。

对于法二:相较之于法一,法二简单粗暴,直接明了,易于想到和实现;但运行时要占用不少的内存,运行大的数据要花费很长的时间。

至此,整个题目解答完毕!!!

  结语:以上就是我对这个问题的理解、解法,可能存在着更好、更简洁的解法代码,希望大家提出来,我们一起讨论,交换看法,共同进步。若上述代码中存在问题,望大家指正,谢谢大家看到结尾。(∩^∩)

奋斗的2351

发布了12 篇原创文章 · 获赞 3 · 访问量 389

猜你喜欢

转载自blog.csdn.net/weixin_45620022/article/details/104943121