AcWing 1230.K倍区间(详解)

[题目概述]

给定一个长度为 N的数列, A 1 , A 2 , … A N A_1,A_2,…A_N A1,A2,AN,如果其中一段连续的子序列 A i … A j A_i…A_j AiAj 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K倍区间。
你能求出数列中总共有多少个 K倍区间吗?

输入格式

第一行包含两个整数 N和 K。以下N行每行包含一个整数 A i A_i Ai

输出格式

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

数据范围

1 ≤ N, K ≤100000,
1 ≤ A i A_i Ai ≤100000

  • 分析问题
    • 一眼就可看出这个题考察前缀和,可以别枚举左右端点来求中间的和,但结合题目所给数据范围1 ≤ N ≤100000 可知,我们最多可以枚举一次,否则大概率会超时,那么怎样进行优化呢?
    • 我们此时的第二重循环就是当i 固定时,在1 ~ i 中有多少s[j - 1]与s[i] 对k的余数相同 (同余定理:数论中的重要概念。给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对m同余),稍加变换就是求0 ~ i - 1中有多少s[j]与s[i] 对k的余数相同,也就是在所有前缀和中,有多少前缀和是余数相同的,这样就优化了一重循环。
       for(int i = 1; i <= n; i ++)
       	for(int j = 1; j <= i; j ++)
       		if((s[i] - s[j - 1]) % k == 0)
       			cnt ++;
    
  • 完整代码(详解版)
#include 
#include 
#include 
using namespace std;
const int N = 100005;
int n, k;
 // 最多10^5个数,每个数最大10^5,而int最大能接受10^9,所以可能会爆int,需要用long long
 // cnt[ ]记录相同余数的数有多少个
long long s[N], a[N], cnt[N];

int main() {
	cin >> n >> k;
	// 预处理前缀和
	for (int i = 1; i <= n; i ++) {
		cin >> a[i];
		s[i] = s[i - 1] + a[i];
	}
	int res = 0;
	cnt[0] = 1; // 余数为0的数本来就有一个,需要初始化为1(子序列为空时)
	for (int i = 1; i <= n; i ++) {
		res += cnt[s[i] % k];
		cnt[s[i] % k] ++; // 当前前缀和的余数也是一种情况,需要更新
	}
	cout << res;
	return 0;
}
  • 本题完毕,有问题的小伙伴可以评论区留言!有帮助的话记得点赞收藏

你可能感兴趣的:(算法,数据结构)