蓝桥杯历年真题: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

问题分析

         这道题难度中等,主要考察我们的数学以及算法的优化能力。从表面上看,我们可以使用嵌套循环暴力求解,时间复杂度为O(n的平方),但是,题目中明确指出,OJ提供的测试数据数量高达100000个,由此看出,暴力求解明显会超出时间要求,所以,我们需要探索一下一种更为简便的求法。

         我们先来举一个简单的例子:在数组区间[i,j]中,我们可以设i<=k<=j,设数组前k个元素的和为sum[k],前j个元素的和和为sum[j],这是,假设如果sum[k]%K == sum[j]%K的时候,我们可以发现,(sum[j] - sum[k])% K ==0 ,由此我们可以看出,在区间[k , j]中的元素之和是K的倍数,所以我们可以利用这个特点来统计在数组[i , j]中,有相同余数的子序列之和的个数,即num[sum[k]%K],便可进行进一步的统计操作,举一个简单的例子

  数列 1 2 3 4 5   mod = 2
  对前1个数的和取模, 为1 之前有0个前缀和取模后为1,个数+0
  对前2个数的和取模, 为1 之前有1个前缀和取模后为1,个数+1
  对前3个数的和取模, 为0 之前有0个前缀和取模后为0, 个数+0
  对前4个数的和取模, 为0 之前有1个前缀和取模后为0,个数+1
  对钱5个数的和取模, 为1 之前有2个前缀和取模后为1,个数+2

  到目前为止ans = 4。但是ans应该等于6,因为这样计算后,我们漏掉了前i个数的和取模是k的倍数的情况,即[0,i]区间和是k的倍数,因此,我们要在ans = 4 的基础上 加上前缀和取模后为0的个数 即ans+2 = 6;

#include<iostream>
#include<cstring>
#include<memory>
#include<cstdio>
using namespace std;
long long sum[100000];
int num[100000];

int main()
{
	long long i,N,K,data,ans=0,count=0;
	memset(sum,0,sizeof(sum));
	memset(num,-1,sizeof(num));
	cin>>N>>K;
	cin>>data;
	sum[0]=data;
	num[data%K]++;
	if(data%K==0)
		count++;
	ans+=num[data%K];
	for(i=1;i<N;i++)
	{
	     cin>>data;
		 sum[i]=sum[i-1]+data;
		 num[sum[i]%K]++;
		 if(data%K==0&&sum[i]%K!=sum[i-1]%K||sum[i]%K==0)
			 //记录data本身可以被K整除的情况,但是此处要注意sum[i]%k!=sum[i-1]%K,因为data % K == (sum[i] - sum[i-1]) % K,此处会造成重复项
			 //同时还需要记录sum[i]本身可以被K整除的情况
			 count++;
		 ans+=num[sum[i]%K];
	}
	cout<<ans+count<<endl;
	//system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/BTBO_/article/details/79730090