神奇的一题由神奇的熊熊提供灵感!!!
题目:
题目描述:
有一个神奇的区间名称称为k区间,为什么叫k区间呢,因为这个区间内所有数的和是k的倍数,现在给你一个序列 ,现在需要你求出有多少个k区间。
输入描述:
第一行一个整数n ,和一个整数k ( 1 <= n <= 1000000 , 1 <= k <= 100);
第二行是n个整数 ai, … an ( 1 <= ai <= 100);
输出描述:
输出一个整数 表示有多少个k区间。
用例输入:
5 3
1 2 3 4 5
用例输出:
7
思考:
土豆片感觉比较正确的思路:
一个区间要想是k的倍数,那么肯定取余k是等于0,所以我们理所当然可以想到万能做法———暴力!!!只需要简单的两个for循环就可以非常容易的找到取余k等于0,也就是所有数的和是k的倍数的区间数量,最后用幼儿园学过的加法就可以得到最后的答案!!!
但是这时候白熊看到了土豆片的无脑暴力代码非常无语,并让他滚回去好好看看时间复杂度,这时土豆片才突然发现1 <= n <= 1000000,时间复杂度竟然有1e6*1e6!土豆片直接醒悟:如此大的数据暴力是会被玩坏的!
土豆片的再次思考:
既然不能暴力那就需要更高级的做法,需要区间的和,那就很容易想到我们在处理区间问题里所使用的算法(可能算是?)——前缀和!先使用前缀和对数组进行处理,然后找出前缀和模k的值为0,1,2…k-1的区间数分别求出来,然后分别计算,这里需要用到胎教学过的排列组合的知识(应该不会有人不会吧)把每个前缀和取余k等于零的值的情况累加就能得到答案!!!
例:前缀和模k为1的区间有x个。相当于x个中选两个有几种选法,那么就可以产生C(x,2)个k倍区间(即x(x-1)/2个)。前缀和模k相同的区间中任意挑两个区间可产生一个k倍区间。
有了以上思考,土豆片轻(难)轻(的)松(一)松(批)就写出来以下代码并且有自信一发a掉这题。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,sum[1000005],a[1000005],b[1000005],ans;
signed main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]=sum[i-1]+a[i]; //前缀和
}
for(int i=1;i<=n;i++){
b[sum[i]%k]++; //桶排思想记录每种的数量
}
for(int i=0;i<k;i++){
ans+=b[i]*(b[i]-1)/2; //简单的排列组合知识
}
cout<<ans;
}
但是!!土豆片发现自己写出来的代码连样例都没过!土豆片直接红温!
到底是哪里出了问题?
土豆片的最终思考:
土豆片感觉已经将所有都考虑到了竟然还没有ac,在认真思考了一坤分钟以后,土豆片突然想到,现在计算的都是取余k以后区间和相同的区间,是需要两两配对找到这一类的k区间数量,但是如果取余后区间和为0的话他本身就是一个k区间了,不需要进行两两配对,只需要最后把前缀和取余等于0的个数直接加上就行了!可以对以上代码进行简单修改:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,sum[1000005],a[1000005],b[1000005],ans;
signed main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]=sum[i-1]+a[i]; //前缀和
}
for(int i=1;i<=n;i++){
b[sum[i]%k]++; //桶排思想记录每种的数量
}
for(int i=0;i<k;i++){
ans+=b[i]*(b[i]-1)/2; //简单的排列组合知识,任选两个即可组成一个k区间
}
ans+=b[0];
cout<<ans;
}
样例过了就是过了,土豆片直接提交!
完美结束!
总结:
核心思想:
1.对于区间问题要联想到前缀和与差分来进行先处理。
2.两个前缀和模k的值相同的区间,可以产生一个k倍区间。(注意0的情况)
3.桶排的类似思想来记录每种情况出现的次数。
4.排列组合知识找到最后答案。
撒花撒花~*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。