跳到主要内容

同余定理

amodk=bmodka\mod k = b \mod kabmodk=0|a - b| \mod k = 0

例题

链接: P8649 [蓝桥杯 2017 省 B] k 倍区间

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

Tip

本题的前置题目: 523. 连续的子数组和 前缀和 + 维护左、枚举右

题目描述

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

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

输入格式

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

以下 NN 行每行包含一个整数 AiA_i (1Ai105)(1 \le A_i \le 10^5)

输出格式

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

样例 #1

样例输入 #1

5 2
1
2
3
4
5

样例输出 #1

6

提示

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

代码

wa

前缀和, 后朴素遍历 O(n2)O(n^2)

#include <cstdio>
#include <vector>

using namespace std;

int main() {
int n, k;
scanf("%d %d", &n, &k);

vector<int> arr(n);
vector<int> sumArr(n + 1);
for (int i = 0; i < n; ++i) {
scanf("%d", &arr[i]);
sumArr[i + 1] = arr[i] + sumArr[i];
}

int res = 0;
for (int i = 1; i <= n; ++i) {
for (int j = i; j <= n; ++j) {
if ((sumArr[j] - sumArr[i - 1]) % k == 0)
++res;
}
}

printf("%d\n", res);

return 0;
}
C++

正解

同余定理得: 余数为 ii 的有 arr[i]arr[i], 那么他们可以组成区间数量为 Carr[i]2C^2_{arr[i]} (无需考虑边算边更新, 因为我们可以看成区间都是相同的元素(组合数学))

  • 为什么前缀和不做差得 [i,j][i, j] 区间, 而是直接可以使用 [0,i][0, i] 区间 ?!

    • ok, 摊牌了, 世界未解之谜tmd
  • 为什么需要arr[0] = 1;预处理?

    • 如果不预处理, 对于输入 [1, 1, 1], k = 2
    • 那么显然有arr[0] = 1, arr[1] = 2, 然后res = 2才是正解
    • Carr[1]2=(arr[1])(arr[1]1)2=1C^2_{arr[1]}=\frac{(arr[1])(arr[1] - 1)}{2}=1 没问题
    • 但是因为 Carr[0]2=(arr[0])(arr[0]1)2=1×(11)2=0C^2_{arr[0]}=\frac{(arr[0])(arr[0] - 1)}{2}=\frac{1 \times (1-1)}{2}=0 所以需要进行初始化: arr[0] = 1

不开long long见祖宗

#include <cstdio>
#include <vector>

using namespace std;

int main() {
int n, k;
scanf("%d %d", &n, &k);

vector<long long> arr(n);
arr[0] = 1;
long long sum = 0;
for (int i = 0; i < n; ++i) {
long long j;
scanf("%lld", &j);
sum = (sum + j) % k;
++arr[sum];
}

long long res = 0;
for (int i = 0; i < k; ++i) {
res += (arr[i] * (arr[i] - 1)) / 2;
}

printf("%lld\n", res);

return 0;
}
C++
请作者喝奶茶:
Alipay IconQR Code
Alipay IconQR Code
本文遵循 CC CC 4.0 BY-SA 版权协议, 转载请标明出处
Loading Comments...