离散数学 习题篇 —— k倍区间

题目:

给定一个长度为N的数列A​1, A2, ⋯, AN, 如果其中一段连续的子序列Ai, Ai+1, ⋯, Aj(i≤j)之和是K的倍数,我们就称这个区间[i,j]是K倍区间。

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

输入格式:

第一行包含两个整数N和K。(1≤N,K≤100000)
以下N行每行包含一个整数Ai。(1≤Ai≤100000)

输出格式:

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

输入样例:

5 2

1

2

3

4

5

输出样例:

6

题解:
题目说是区间 [i, j] 之间的和取模k等于0,问有多少这样的区间。

那么这个区间和怎么求呢?
想一下,从第i个元素加到第j个,就相当于Sj - Si-1(Sn是区间 [0, n]所有元素的和下同)。那么这个题就变成了(Sj - Si-1)%k = 0的区间个数。根据同余定理,左边的式子又能化为Sj%k - Si-1%k = 0.也就是说,Sj%k = Sj-1%k

接下来就是计数了,这里举个例子,统计一下在某个点模除k的值,假设不为0,如果只有一个点模除k不为0, 能够组成一个符合题意的区间么,显然不行,因为只有个点,不能构成一个区间。如果有两个点呢?可以构成1个。 如果有三个点呢?可以构成3个(第一个点和第二个点,第二个点和第三个点,第一个点和第三个点),如果有四个点呢?可以构成6个(第一个和第二个,第二个和第三个,第三个和第四个,第一个和第三个,第一个和第四个,第二个和第四个)
理一下:1对应0,2对应1(0 + 1),3对应3(0 + 1 + 2),4对应6(0 + 1 + 2 + 3)。综上,如果模除k为x的点的个数为n,那么答案就有 ∑ 0 n \displaystyle\sum_0^n 0n个。
但这只是一个点的,把所有点的加起来就行了……吧?
不行,刚刚只是模除后不等于0的,还有等于0的情况呢,如果等于零的话,我们再举例看看,还是只是三个点,可以构成第一个和第二个,第二个和第三个,第一个和第三个还有第一个本身,第二个本身,第三个本身,也就是比之前计数多了三个,即在最后的答案中加上等于0的个数。

C++版:

#include 
using namespace std;
const long long MAXN = 1e6 + 10;
int num[MAXN], cnt[MAXN];
int main(int argc, char const *argv[])
{
    long long n, k, ans = 0;
    cin >> n >> k;
    for(int i = 1; i <= n; ++i)
    {
        int a;
        cin >> a;
        num[i] = (num[i - 1] + a) % k;
        ans += cnt[num[i]] ++;
    }
    ans += cnt[0];
    cout << ans;
    return 0;
}

你可能感兴趣的:(离散数学笔记及习题)