問題描述
給定一個長度為 $n$ 的正整數陣列,計算總和為 $x$ 的子陣列數量
練習題
CSES - Subarray Sums I
這題保證正整數,所以加上某值之後,總和必定比原本大;減掉某值之後,總和必定比原本小。
可以用 Sliding Widnow 來解,若目前總和比 $x$ 小,就將右指標往右一格;若目前總和比 $x$ 大,就將左指標往右一格;如果目前總和就是我們要的,把答案加上去後,將左指標右移一格(或右指標右移一格)。
時間複雜度 $O(n)$
完整程式碼
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main() {
int n;
ll x;
cin >> n >> x;
int arr[200005];
for (int i = 0; i < n; i++) cin >> arr[i];
int left = 0;
int right = -1;
ll sum = 0;
int ans = 0;
while (right < n) {
while (right < n && sum < x) {
right++;
sum += arr[right];
}
while (left <= right && sum > x) {
sum -= arr[left];
left++;
}
if (sum == x) {
ans++;
sum -= arr[left];
left++;
}
}
cout << ans;
}
CSES - Subarray Sums II
與上題不一樣的是,這題不保證正整數,所以加上某值之後,總和未必比原本大;減掉某值之後,總和未必比原本小,因此無法使用 Sliding Window 維護,可以轉而用 前綴和 + Map。
設 $[0,j]$ 的總和為 prefix[j],$[0,i]$ 的總和為 prefix[i],且$[i+1,j]$ 的總和為 $x$
那麼此式成立: prefix[j] - prefix[i] = X
移項可得: prefix[j] - X = prefix[i]
因此我們只要將 prefixSum 加入 map 中,並找 prefixSum - X 的前綴和有幾個即可。
時間複雜度 $O(nlogn)$
完整程式碼
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main() {
int n;
ll x;
cin >> n >> x;
int arr[200005];
for (int i = 0; i < n; i++) cin >> arr[i];
map<ll, ll> prefixSum;
prefixSum[0] = 1;
ll cnt = 0;
ll prefix = 0;
for (int i = 0; i < n; i++) {
prefix += arr[i];
cnt += prefixSum[prefix - x];
prefixSum[prefix]++;
}
cout << cnt;
}
CSES - Subarray Divisibility
這題是要找有多少個子陣列,其所有元素的總和可以被 n 整除。
可以沿用上一題的方法,前綴和 + Map,但查詢的東西不太一樣。
若 sum(i+1,j) = prefix[j] - prefix[i]
如果這個子陣列要被 n 整除,則: (prefix[j] - prefix[i]) mod n = 0
也就是:
prefix[j] mod n = prefix[i] mod n
這表示如果兩個前綴和模 $n$ 的結果相同,則它們中間的子陣列就符合條件
時間複雜度 O(nlogn)
負數取模
C++ 中 $-7$ % $3 = -1$
這會導致我們無法安全地把 % 結果當作 map 的 key(因為 key 應該在 0 ~ n-1 之間)
可以使用這個技巧來正規化負數:
((prefix + arr[i]) % n + n) % n
這樣無論 prefix 是正或負,都會落在 0 ~ n-1 之間
((2 - 3) % 4 + 4) % 4 = 3 不是 -1
換種想法:
目標為 5 餘數為 4,代表還要 +1 才能達到目標(5)
目標為 5 餘數為 -1,代表還要 +1 才能達到目標(0),所以他要被當成餘數為 4
若以為 -1 換成餘數為 1 的話,代表要 +4 才可以達到目標,但 -1 + 4 為 3 並不符合
完整程式碼
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main() {
int n;
cin >> n;
int arr[200005];
for (int i = 0; i < n; i++) cin >> arr[i];
map<ll, ll> prefixSum;
prefixSum[0] = 1;
ll cnt = 0;
ll prefix = 0;
for (int i = 0; i < n; i++) {
prefix = ((prefix + arr[i]) % n + n) % n;
cnt += prefixSum[prefix];
prefixSum[prefix]++;
}
cout << cnt;
}