快轉到主要內容

CSES-1662 Subarray Divisibility

標籤: 前綴和
目錄


題目連結:https://cses.fi/problemset/task/1662

題意
#

題目給定長度為 \( n \) 的整數陣列。
需要求出有幾個「連續子陣列(Subarray)」的數字總和可被 \( n \) 整除(即除以 \( n \) 的餘數為 0)。

思路
#

子陣列 [L, R] 的總和可表示為 pre[R] - pre[L-1]
若該總和能被 \( n \) 整除,則滿足:
\( (\text{pre}[R] - \text{pre}[L-1]) \pmod n \equiv 0 \)
依據同餘性質,上式等價於:
\( \text{pre}[R] \pmod n \equiv \text{pre}[L-1] \pmod n \)

這意味著,只需關注「每個前綴和除以 \( n \) 的餘數」。
當某位置的前綴和餘數,與過去某位置的前綴和餘數相同時,兩者之間的子陣列總和必能被 \( n \) 整除。

實作流程如下:

  1. 建立大小為 \( n \) 的陣列 cnt,記錄每個餘數出現的次數。初始化 cnt[0] = 1,代表初始狀態下餘數為 0 發生過一次。
  2. 遍歷陣列計算前綴和,並求取對於 \( n \) 的餘數。
  3. 由於 C++ 中負數取餘的結果可能為負,因此需要使用 (pre[i] % n + n) % n 將餘數轉換為正數。
  4. 取得正確的餘數後,查詢 cnt 中該餘數出現過的次數並累加至答案,接著更新該餘數的計數。

整體時間複雜度為 \(\mathcal{O}(N)\)。

程式碼
#

#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define fi first
#define se second
#define INF LONG_LONG_MAX/1000
#define WA() cin.tie(0)->sync_with_stdio(0)
#define all(x) (x).begin(), (x).end()
#define int long long
#define PII pair<int, int>

signed main() { WA();
    int n; cin >> n;
    vector<int> v(n);
    for (auto &i : v) cin >> i;
    int ans = 0;
    
    // pre 用來記錄前綴和,cnt 用來統計「每個餘數」曾經出現過的次數
    vector<int> pre(n+1), cnt(n); 
    
    // 初始化 `cnt[0]` 為 1,代表前綴和為 0 的初始狀態
    cnt[0] = 1;
    
    // 計算前綴和
    for (int i = 1; i <= n; i++) pre[i] = v[i-1]+pre[i-1];
    
    for (int i = 1; i <= n; i++) {
        // 處理 C++ 負數取餘的轉換
        int now = (pre[i]%n+n)%n;
        
        // 若餘數相同,代表兩者差距可被 n 整除
        ans += cnt[now];
        
        // 登記該餘數供新子陣列配對
        cnt[now]++;
    }
    
    // 輸出答案
    cout << ans;
}

// 核心精神:(pre[R] - pre[L-1]) % n == 0 => pre[R] % n == pre[L-1] % n
Piau 的筆記本
作者
Piau 的筆記本
希望我寫下來的東西能夠長久的記在我的腦中