【洛谷 P8649】[蓝桥杯 2017 省 B] k 倍区间 题解(前缀和+同余定理+组合数学)

[蓝桥杯 2017 省 B] k 倍区间

题目描述

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

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

输入格式

第一行包含两个整数 N N N K K K ( 1 ≤ N , K ≤ 1 0 5 ) (1 \le N,K \le 10^5) (1N,K105)

以下 N N N 行每行包含一个整数 A i A_i Ai ( 1 ≤ A i ≤ 1 0 5 ) (1 \le A_i \le 10^5) (1Ai105)

输出格式

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

样例 #1

样例输入 #1

5 2
1  
2  
3  
4  
5

样例输出 #1

6

提示

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


思路

首先定义一些全局变量,包括数列的长度 n n n,倍数 k k k,数列 a a a,前缀和数组 p s ps ps,以及一个映射 mcnt 来存储前缀和模 k k k 的结果的计数。

main 函数中,首先初始化 ps[0] 为 0,mcnt[0] 为 1。初始化 mcnt[0] 为 1,就相当于在所有的前缀和中预先加入了一个和为 0 的前缀和,这样就能正确计算出那些直接就是 k k k 倍数的子序列数量。

然后读取 n n n k k k 的值,接着读取数列 a a a 的各个元素,并计算其前缀和,同时更新 mcnt 映射。

计算前缀和的目的是为了快速得到任意一个子序列的和,而 mcnt 映射的目的是为了快速找到前缀和模 k k k 结果相同的子序列。由同余定理可知,如果两个前缀和模 k k k 的结果相同,那么这两个前缀和对应的子序列之间的序列和必定是 k k k 的倍数。

接着遍历 mcnt 映射,对于映射中的每一个值,如果其计数大于 1,那么就说明存在多个前缀和模 k k k 的结果相同,也就是存在多个和为 k k k 的倍数的连续子序列。这些子序列可以任意两两组合,所以可以用组合公式 C n 2 = n ( n − 1 ) / 2 C_n^2 = n(n-1)/2 Cn2=n(n1)/2 来计算子序列的数量,其中 n n n 是前缀和模 k k k 结果相同的数量。

最后输出计算得到的和为 k k k 的倍数的连续子序列的总数量。

注意

要开 long long


AC代码

#include 
#include 
#include 
#define AUTHOR "HEX9CF"
using namespace std;
using ll = long long;

const int N = 1e6 + 7;
const int INF = 0x3f3f3f3f;
const ll MOD = 1e9 + 7;

int n, k;
int a[N];
ll ps[N];

map<int, ll> mcnt;

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	ps[0] = 0;
	mcnt[0] = 1;

	cin >> n >> k;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		ps[i] = ps[i - 1] + a[i];
		mcnt[ps[i] % k]++;
	}

	ll ans = 0;
	for (const auto m : mcnt) {
		// cout << m.first << " " << m.second << "\n";
		if (m.second > 1) {
			ll s = m.second;
			ans += (s * (s - 1)) >> 1;
		}
	}
	cout << ans << "\n";
	return 0;
}

你可能感兴趣的:(Algorithm,Problems,蓝桥杯,职场和发展)