P8649 [蓝桥杯 2017 省 B] k 倍区间
题目:
思路:
以后看到子序列问题,就应该想想前缀和,而为了判断是否是k的倍数,则会想到求模运算,在求模运算中,有一个经典的同余定理
前缀和
1. 什么是前缀和
所谓前缀和,就是:s[i] = s[i - 1] + a[i](i >=2)。
例如样例 A = {1, 2, 3, 4, 5}
那么前缀和 S = {1, 3, 6, 10, 15}。
2. 前缀和性质
不仅Si是A的一个子序列,Si - Sj 也是A的一个子序列,Aj+1 + Aj+2 +…+Ai这个子序的和,例如(下标从0开始):
S3 - S1 = 7
= A2 + A3 = 3 + 4
同余定理
如果两个数a和b他们对k取模,余数相同(即a mod k = b mod k),那么他们两个的差(a - b)对k取模为0。即:
(a - b) % k == 0
a - b 是k的倍数
解决了储备知识,下面正式开始解题!
显然根据前缀和的结构,如果前缀和 Si % k == 0 那么意味着这是一个满足题意的子区间,拿题目样例来看,S2 = 6 和 S3 = 10都满足,这里找到了两个,剩下的都不满足,那还能怎么利用前缀和呢?或者说,剩下满足题意的子序列和怎么找?
结合前缀和数组性质:S3 - S2 也是一个子序和,而且刚好等于4,并且4能被2整除!这似乎是一个惊人的发现!
即:余数都为0的两个前缀和数相减对2取余也为0
继续深入,前缀和数组中,S0、S1、S4他们模2的余数相同(都为1)!这时立马想到同余定理!即**S0、S1、S4两两组合**相减之后,对2取余不是刚好等于0吗!
所以:同余的前缀和数两两组合(本质是作差)便是一个可以满足题意的子序列和
而说到组合问题,想到公式:
Cx2 = (x-1 + x)/2,那么除了余数为0的情况下,不做减法本身的前缀和数就已经满足条件,在计算时要额外加x个,其他余数的情况就是直接算组合数公式即可。
代码:
Java版:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
public static void main(String[] args) throws Exception {
in.nextToken();
long n = (long) in.nval;
in.nextToken();
long k = (long) in.nval;
int count = 0;
long t = 0;
long[] modS = new long[(int) n];
for(long i = 0; i < n; i++ ) {
in.nextToken();
t += (long) in.nval;
modS[(int) (t % k)]++;
if(t % k == 0) { // 当前缀和数模k为0时,前缀数本身也需要加上
count++;
}
}
for(long i : modS) {
count += (i - 1) * i / 2;
}
out.print(count);
out.close();
}
}
但是Java的不知道为什么,有一个测试数据就是过不了,可能是溢出了,这里就不花时间优化了,这本来是一道C语言组的题。