AcWing
1230. K倍区间
暴力枚举区间,逐个元素求和,时间复杂度$O(n^3)$,打咩;
暴力枚举区间,前缀和数组求区间和,时间复杂度$O(n^2)$,还是打咩;
观察到一个性质,从小到大枚举右端点r
,假设此时的区间和s[r]
满足s[r] % k == a
,对于r
左端所有的点l∈[0, r-1]
,如果其区间和s[l]
也满足s[l] % k == a
,那么(s[r] - s[l]) % k == 0
,即s[l+1, r]
是一个k倍区间。所以只需要在遍历的同时,记录0 ~ k-1
这些余数出现的次数,在遍历到i
时,求出s[i] % k
,看i
前面的前缀和数组中该余数出现的次数,就是以i
为区间右端点时所有的K
倍区间数,之后再更新次数数组cnt
中该余数的值,令其+1
. 注意初始化cnt[0] = 1
,因为s[0] % k == 0
,而遍历从1
开始(对应第一个s[i] % k == 0
的情况,res
应该+1
)。
这样做的时间复杂度只有$O(n)$,nice.
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
int n, k;
const int N = 100010;
typedef long long LL;
LL s[N];
int cnt[N];
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> k;
for(int i = 1; i <= n; i++){
cin >> s[i];
s[i] += s[i - 1];
}
cnt[0] = 1;
LL res = 0;
for(int i = 1; i <= n; i++){
res += cnt[s[i] % k];
cnt[s[i] % k]++;
}
cout << res;
return 0;
}