数列
题目描述:
给你一个长度为\(N\)的正整数序列,如果一个连续的子序列,子序列的和能够被\(K\)整除,那么就视此子序列合法,求原序列包括多少个合法的连续子序列?对于一个长度为8的序列,\(K=4\)的情况:2, 1, 2, 1, 1, 2, 1, 2 。它的答案为6,子序列是位置1->位置8,2->4,2->7,3->5,4->6,5->7。
输入格式:
第一行:\(T\),表示数据组数
对于每组数据:
第一行:2个数,\(K\),\(N\)
第二行:\(N\)个数,表示这个序列
输出格式:
共T行,每行一个数表示答案
样例输入:
2 7 3 1 2 3 4 8 2 1 2 1 1 2 1 2
样例输出:
0 6
数据范围:
100%数据满足
\(1<=T<=20\)
\(1<=N<=50000\)
\(1<=K<=1000000\)
\(序列的每个数<=1000000000\)
30%数据满足
\(1<=T<=10\)
\(1<=N,K<=1000\)
时间限制:
1S
空间限制:
256M
提示:
remove!!!
题解
连续子序列的和能被\(K\)整除的个数?
\(n^2\)暴力?\(1<=N<=50000\),貌似过不了 。
如果我们维护一下这一堆数前一些数的某一些性质,每加入一个数更新一下这些性质。(别问我怎么想到的,本来想到dp或者贪心,然后想着想着就想到了)
在每次加入一个数时,只要找到在这个数前面的某一些点到这个数的连续序列能被\(K\)整除的数量。
那么我们需要的值就是当前数之前的所有后缀和。
我们可以用一个数组\(kk[i]\)表示后缀和在模\(K\)意义下后缀和为\(i\)的个数,每次加入一个数的时候求出一个数\(x\)使加入的数加上\(x\)后模\(K\)为0,让\(ans\)加上\(kk[x]\)就行了。
但是我们发现加入一个新的数时更新\(kk\)所需要的时间是\(O(n)\)的,
这里我们用一个\(sum\),每次加入新数时\(sum\)就加上这个数,那么\(kk[i]\)就表示后缀和在模\(K\)意义下后缀和为\(i+sum%K\)的个数。
那么在把新数加入\(kk\)时就变成\(kk[(a-sum)%K]++\)了。(\(a\)为新数)
这么做的时间复杂度好像是\(O(n)\)的。
上代码:
#include<bits/stdc++.h>
using namespace std;
int t,k,n,a[50009];
int sum;
int ans;
int kk[1000009];
int main(){
scanf("%d",&t);
while(t--){
memset(kk,0,sizeof(kk));
ans=sum=0;
scanf("%d%d",&k,&n);
for(int j=1;j<=n;j++){
scanf("%d",&a[j]);
a[j]%=k;
ans+=kk[(k-((a[j]+sum)%k))%k];//(a[j]+sum)%k可能是0,所以k-(a[j]+sum)%k可能为k,所以最后还要%k
sum+=a[j];
sum%=k;
kk[(a[j]-sum+k)%k]++;
if(a[j]%k==0) ans++;//要注意序列里只有一个点的时候也要加到$ans$里去
}
printf("%d\n",ans);
}
return 0;
}