给定一个长度为 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(i≤j) 之和是 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) (1≤N,K≤105)。
以下 N N N 行每行包含一个整数 A i A_i Ai ( 1 ≤ A i ≤ 1 0 5 ) (1 \le A_i \le 10^5) (1≤Ai≤105)。
输出一个整数,代表 K K K 倍区间的数目。
5 2
1
2
3
4
5
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(n−1)/2 来计算子序列的数量,其中 n n n 是前缀和模 k k k 结果相同的数量。
最后输出计算得到的和为 k k k 的倍数的连续子序列的总数量。
要开 long long
#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;
}