问题描述
你能求出数列中总共有多少个K倍区间吗?
以下N行每行包含一个整数Ai。(1 <= Ai <= 100000)
1
2
3
4
5
问题分析
这道题难度中等,主要考察我们的数学以及算法的优化能力。从表面上看,我们可以使用嵌套循环暴力求解,时间复杂度为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;
}