【NOIP17提高模拟训练11】数列

题目链接

数列

题目描述

给你一个长度为\(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;
}

猜你喜欢

转载自www.cnblogs.com/linjiale/p/11308931.html