AcWing_1230 K倍区间(前缀和)

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;
}