给定一个长度为 NN 的数列,A1,A2,…AN 如果其中一段连续的子序列 Ai,Ai+1,…Aj 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K 倍区间。
你能求出数列中总共有多少个 K 倍区间吗?
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行包含一个整数 Ai。
输出格式
输出一个整数,代表 K 倍区间的数目。
数据范围
1≤N,K≤1000001≤N,K≤100000,
1≤Ai≤100000
特别暴力做法:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std ;
using ll = long long ;
int n , mod ;
const int N = 1e5 + 8 ;
int sum[N] ;
ll res ;
ll cnt[N] ;
int main(){
std::ios::sync_with_stdio(false) ;
std::cin.tie(0) ;
cin >> n >> mod ;
int x ;
for(int i = 1 ; i <= n ; i ++) {
cin >> sum[i] ;
}
int res = 0 ;
for(int r = 1 ; r <= n ; r ++) {
for(int l = 1 ; l <= r ; l ++) { //枚举所有区间
int ans = 0 ;
for(int i = l ; i <= r ; i ++) {
ans += sum[i] ;
}
if(ans % mod == 0) res ++ ;
}
}
cout << res << '\n' ;
return 0 ;
}
不是那么暴力但是也挺暴力的做法:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std ;
using ll = long long ;
int n , mod ;
const int N = 1e5 + 8 ;
int sum[N] ;
ll res ;
ll cnt[N] ;
int main(){
std::ios::sync_with_stdio(false) ;
std::cin.tie(0) ;
cin >> n >> mod ;
int x ;
for(int i = 1 ; i <= n ; i ++) {
cin >> x ;
sum[i] = sum[i - 1] + x ; //存个前缀和数组
}
int res = 0 ;
for(int r = 1 ; r <= n ; r ++) {
for(int l = 1 ; l <= r ; l ++) {
if( !((sum[r] - sum[l - 1]) % mod) ) res ++ ;
}
}
cout << res << '\n' ;
return 0 ;
}
虽然用了前缀和优化,但是也会TLE,无法过掉所有的数据:
那么如何再降低时间复杂度呢?基于前面前缀和的思想,我们在前缀和里面是为了找到一组满足这个公式的数据 ,那么根据同余公式我们可以直接化简为找一个
的左右端点。
那么我们在第一层循环右端点r的时候,可以用一个数组存sum[r] % k 的值出现的次数,然后加上即可,我们以样例为例:
/*
5 2
1 2 3 4 5
1 3 6 10 15 //前缀和
%k : 1 1 0 0 1 //%k以后的值
*/
1:%k后的值是1,之前没有出现过
2:%k后的值是1,之前出现过一次,res ++ ;
3:%k后的值是0,之前没有出现过
4:%k后的值是0,之前出现过一次,res ++ ;
5:%k后的值是1,之前出现过两次,res += 2 ;
但是这样算,最后的res结果是4,似乎不符合题目要求。原来是因为,我们在计算%k后的值的时候,如果结果是0,那么就说明,从开始到目前为止就已经是一个复合条件的区间了,即[1 , r]就是一个符合条件的区间了,所以最后的结果要加上%k后值为0的所有次数,那么样例中%k后出现了两次结果为0的前缀和数组,那么最后的结果就是res + 2 ,复合答案要求。
优化代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std ;
using ll = long long ;
int n , mod ;
const int N = 1e5 + 8 ;
ll sum[N] ; //记得要开long long ,不然会爆
ll res ;
ll cnt[N] ;
int main(){
std::ios::sync_with_stdio(false) ;
std::cin.tie(0) ;
cin >> n >> mod ;
int x ;
for(int i = 1 ; i <= n ;i ++) {
cin >> x ;
sum[i] = sum[i - 1] + x ; //构造前缀和数组
}
for(int r = 1 ; r <= n ; r ++) {
res += cnt[sum[r] % mod] ; //这里一定要先加,然后再更改cnt的值
cnt[sum[r] % mod ] ++ ; //用cnt来记录%k后的值出现了几次
}
cout << res + cnt[0] << '\n' ; //+cnt[0]就是加上%k后的值为0的所有组合
return 0 ;
}