【纪中模拟2019.08.01】【JZOJ2644】数列

题目链接

题意:

  在一个长度为$N$的正整数序列$\{a_i\}$中,定义一个“合法”的子区间,其和能够被$K$整除。求原序列包括多少个“合法”的子区间?

  共$T$组数据。

  $1\le T\le 20,\quad 1\le N\le 5*10^4,\quad 1\le K \le 10^6,\quad 1\le a_i\le 10^9$

 

分析:

  乍一看想不到$O(n\,logn)$的做法,先写一写$O(n)\sim O(n^2)$的暴力吧。

  这里$O(n)$是预处理前缀和的时间,$O(n^2)$是枚举区间端点的时间。每次发现一个“合法”的子区间就$ans++$。

  思考一下,我们这么做是基于这样一个式子:$$pre[r]-pre[l-1]=\sum\limits^{r}_{i=l}a_i\equiv 0\;(mod\,K)$$

  那么将它改写一下,显然有$$pre[r]\equiv pre[l-1]\;(mod\,K)$$

  垂死病中惊坐起,此题开桶我看行!前缀和模$K$同余的任意两个位置作为端点所组成的区间都是“合法”的,于是我们开桶对模$K$各个余数进行计数,运用组合数求答案。

  时间复杂度O(K),空间复杂度O(N+K)。

 

实现(100分):

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define IL inline
using namespace std;
const int N=5e4;
const int K=1e6;
typedef long long LL;

    int n,T;
    LL k,a[N+3],s[N+3],c[N+3],ans;
    int cnt[K+3];

int main(){
    c[0]=c[1]=0;    c[2]=1;
    for(int i=3;i<=N;i++)
        c[i]=c[i-1]+i-1;

    scanf("%d",&T);
    while(T--){
        scanf("%lld%d",&k,&n);
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        
        memset(cnt,0,sizeof cnt);
        s[0]=0;    cnt[0]=1;
        for(int i=1;i<=n;i++){
            s[i]=(s[i-1]+a[i])%k;
            cnt[s[i]]++;
            
        }
        
        ans=0;
        for(int i=0;i<k;i++)
            ans+=c[cnt[i]];
        
        printf("%lld\n",ans);
        
    }

    return 0;

}
View Code

 

 

小结:

  对于计数问题,我们通常可以把原理式进行变形,找到规律,就可以从暴力$ans++$进化了。

 

posted @ 2019-08-06 21:36  汉谡  阅读(141)  评论(0编辑  收藏  举报