K倍区间

蓝桥杯 2017 省B

Problem Statement

给定一个长度为 $N$ 的数列,$A_1,A_2, \cdots A_N$,如果其中一段连续的子序列 $A_i,A_{i+1}, \cdots A_j(i \le j)$ 之和是 $K$ 的倍数,我们就称这个区间 $[i,j]$ 是 $K$ 倍区间。

你能求出数列中总共有多少个 $K$ 倍区间吗?

Input

第一行包含两个整数 $N$ 和 $K$$(1 \le N,K \le 10^5)$。

以下 $N$ 行每行包含一个整数 $A_i$$(1 \le A_i \le 10^5)$。

Output

输出一个整数,代表 $K$ 倍区间的数目。

Sample Input

1
2
3
4
5
6
5 2
1  
2  
3  
4  
5

Sample Output

1
6

Constrains

时限 2 秒, 256M。蓝桥杯 2017 年第八届

Solving

首先我们注意到直接暴力枚举会超时O($N^2$)

所以我们需要考虑O(N)复杂度的做法,我们容易发现一个性质,如果两个前缀和 prefix[i]和prefix[j],他们若%k的结果相同,那么他们两个相减一定会有:

$$ (prefix[j] - prefix[i])\%k=0 $$

所以我们可以维护一个桶,记录每个前缀和%k余数的个数,最后对每个桶中的出现次数计算C(x,2)并相加即可,注意,当一个数直接是k的倍数时,以它本身作为的长度为一的区间也算一个有效区间,这样我们仅需要初始化mp[0]=1, 这样在计算C(x,2)时,才会计算出正确答案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const int N = 1e5+10;
const int M = 0;
int n,k; 
void solve()
{
    cin >> n >> k;
    int arr[n+1];
    for(int i=1;i<=n;i++) cin >> arr[i];
    int prefix[n+1];
    map<int,int> mp;
    mp[0] = 1;//一个数也算有效区间
    for(int i=1;i<=n;i++) prefix[i] = prefix[i-1] + arr[i];
    int ans = 0;
    for(int i=1;i<=n;i++)
    {   
        mp[prefix[i]%k]++;//记录余数出现的次数
    }
    for(auto x : mp)
    {
        int val = x.second;
        ans += (val*(val-1))/2;//计算每个桶下的C(val,2)
    }
    cout << ans;
}
Licensed under CC BY-NC-SA 4.0
Member of the Qilu University Of Technology ACM-ICPC Association
Built with Hugo
Theme Stack designed by Jimmy