---
# System prepended metadata

title: 前後綴和與差分

---

## 前綴和定義
前綴和的定義就是各個元素到起點的總和，下圖的陣列 A 對應的前綴和就是 P
![image alt](https://miro.medium.com/v2/resize:fit:828/format:webp/1*kJuyMrGzh9MEy3LXC2NL9w.jpeg =60%x)
假如有一個陣列 $A$，裡面有 $n$ 項，假設它的前綴和為陣列 $P$
那麼 $P$ 看起來會像這樣
```cpp=
P[0] = A[0]
P[1] = A[0] + A[1]
P[n-1] = A[0] + A[1] + ... + A[n-1]
```

而後綴和其實就是各個元素到終點的總和
```cpp=
P[n-1] = A[n-1]
P[n-2] = A[n-1] + A[n-2]
P[0] = A[n-1] + A[n-2] + ... + A[0]
```

所以任一位置的前綴和都可以說是 : 上一個位置的前綴和 + 目前位置的值
當然為了避免在計算第一個位置時超出範圍，我們可以在最前方加入一個 $0$

通常來說一般在計算前綴和都是使用一個 for-loop
```cpp=
vector<int> A = {1, 2, 3, 4, 5} ;
vector<int> P(5) ;
P[0] = A[0] ;

cout << P[0] << ' ' ;
for (int i=1;i<A.size();i++) {
    P[i] = P[i-1] + A[i] ;
    cout << P[i] << ' ' ;
}
```

前綴和最基本的題目就是從定義解題，但是之後的延伸題會加入其他的東西
尤其許多題目會與區間有關連，這時候前綴和最好在前方加入一個 $0$ 表示無任何元素時的前綴和

## P-1 等值首尾和
[題目連結](https://zerojudge.tw/ShowProblem?problemid=d563)
假設有一個陣列 $x$，它有 $n$ 個元素，每一個都大於零
我們說 `x[0]+x[1]+...+x[i]` 是個前綴和(Prefix Sum)，而`x[j]+x[j+1]+...+x[n-1]`則是個後綴和(Suffix Sum)
請寫一個程式，求出 $x$ 中有多少組相同的前綴和與後綴和

解題思路:
圖片
首先題目要求找到前綴和後綴和的相同值有幾組
可是如果題目中有相同的組合(例如:兩組 $3+6+2$ 都等於 $11$ )這樣只算是 $1$ 組
而且因為每一項都大於零，所以前綴和往後只會越來越大，後綴和往前只會越來越小
所以在找數字 $x$ 的時候，後綴和只要從最後面往前找就行，如果還要找 $x+y$ 的數字
那只要在原本找到 $x$ 的地方再往前找即可



用程式碼看起來就像這樣：
```cpp=
#include<bits/stdc++.h>
using namespace std ;

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0) ;

    int n ;
    cin >> n ;
    vector<int> A(n), pre(n), suf(n) ; // 數值、前綴和、後綴和

    for (int i=0;i<n;i++)
        cin >> A[i] ;

    pre[0] = A[0], suf[n-1] = A[n-1] ;

    for (int i=1;i<n;i++) {
        pre[i] = pre[i-1] + A[i] ;         // 計算前綴和
        suf[n-i-1] = suf[n-i] + A[n-i-1] ; // 計算後綴和
    }

    int idx = n-1, ans = 0 ;

    for (int i=0;i<n && idx >= 0;i++) {
        while (idx >= 0 && pre[i] > suf[idx]) // 往前找
            idx-- ;
        if (idx != -1 && pre[i] == suf[idx]) // 找到了
            ans++ ;
    }
    cout << ans ;

    return 0 ;
}
```
## P-2 區間和練習 
[題目連結](https://zerojudge.tw/ShowProblem?problemid=e346)
給定一個長度為 $n$ 的整數序列 $A$，請回答 $q$ 筆詢問
每筆詢問會問其中一段連續區間的總和為何

解題思路：
區間和可以利用前綴和計算而成
拿範例輸入來說，設前綴和為陣列sum
因為他給的區間是從 1 開始，而我們的 index 是從 $0$ 開始，所以他給的數字要 $-1$
`sum[ 1 ] = A[ 0 ] + A[ 1 ]`
`sum[ 4 ] = A[ 0 ] + A[ 1 ] + A[ 2 ] + A[ 3 ] + A[ 4 ]`
`sum[ 5 ] - sum[ 1 ] = A[ 2 ] + A[ 3 ] + A[ 4 ]`
也就是 [ $3,5$ ] 的**區間和**，上述式子可以看出**前綴和跟區間和的關係**
用程式碼看起來就像這樣：
```cpp=
cout << sum[5] - sum[3-1] << '\n' ;
```
區間和可能有 [ $1,n$ ] 在這種狀況下 $1-2 = -1$ 會導致輸出錯誤
所以我們在前綴和前面加上一項 $0$
並且因為前綴和多了一項，我們也要新增預設
```cpp=
prefix_sum[0] = 0 // 前綴和預設
```
這樣解題就很快速了，不管他問的是多少區間和，只需要帶入function就可以
所以全部的程式碼看起來就像這樣：
```cpp=
#include<bits/stdc++.h>
using namespace std ;
typedef long long LL ;

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0) ;

    int n ;
    cin >> n ;
    vector<LL> pre(n+1) ;
    pre[0] = 0 ;

    for (int i=1;i<=n;i++) { // 計算前綴和
        cin >> pre[i] ;
        pre[i] += pre[i-1] ;
    }

    int q, l, r ;
    cin >> q ;

    for (int i=0;i<q;i++) { // 區間和
        cin >> l >> r ;
        cout << pre[r] - pre[l-1] << '\n' ;
    }

    return 0 ;
}
```
---
接下來就是延伸階段了，前綴和用在二維陣列的情況下
除了有左右找前綴和，還可以有上下找前綴和的變化

## 差分定義
相當於前綴和反過來，如果陣列 $A$ 的前綴和是 $B$，那 $A$ 同時是 $B$ 的差分
主要的用法是處理整個區間的操作，通常是高效率的加減運算
```cpp=
D[0] = A[0]
D[i] = A[i] - A[i-1] // i >= 2 
```
接下來說明一下如何作區間加減法，我們可以先從前綴和的概念去思考，不過先說明最暴力的解法
假設我需要將 $A$ 中區間 `[L, R]` 都加 $5$，那程式碼會像是這樣
```cpp=
for (int i=L;i<=R;i++)
    A[i] += 5 ;
```
如果需要 $m$ 次加減法操作，而陣列長度為 $n$，時間複雜度最糟就會到 $O(nm)$
這時候先回頭去看原本陣列改動後前綴和的改變，假設 `A[3] += 3`，更新後的前綴和也會跟著變化
這時候前綴和 `P[3] ~ P[n-1]` 都必須加上 $3$，換句話說，前綴和的區間 `[3, n-1]` 都加 $3$
這時候就很有趣了，因為原陣列的加法會變成前綴和陣列的區間加法，不過這個區間的結尾一定是 $n-1$
這時候我們考慮看看 `A[6] -= 3`，這時候前綴和 `P[6] ~ P[n-1]` 也都必須減去 $3$
也同樣是整個區間的減法，這樣一來一回，整個前綴和相較最開始，其實只有區間 `[3,6-1]` 有加 $3$
而區間 `[6, n-1]` 一加一減就抵銷了，前面我們說過對於前綴和陣列來說，原陣列其實是它的差分
換句話說，如果我們想要做原陣列的區間加法，應該去改動差分陣列，改動完後在回推新的陣列
區間減法也是同一個邏輯，所以這兩個操作其實就可以總結成下列公式
1. 區間加法: `D[L] += x, D[R+1] -= x` 之後做差分陣列的前綴和(更新後的原陣列)
2. 區間加法: `D[L] -= x, D[R+1] += x` 之後做差分陣列的前綴和(更新後的原陣列)

這樣在多個區間運算的情況下時間複雜度只有 $O(n+m)$
建立差分陣列、還原原陣列都是 $O(n)$，而區間操作每次 $O(1)$ 共改 $m$ 次所以是 $O(m)$

## 二維前綴和、差分
適用於二維陣列的計算方式，可以用於快速計算區域和或是快速做區域加減法，基本用法跟一維一樣
* `P[i][j]` 表示由四個點 $(0,0), (i,0), (0,j), (i,j)$ 圍成方形區域的區域和

所以要算 $(x1, y1)$ 作為左上，$(x2, y2)$ 作為右下的方形區域和
就要用 `P[x2][y2]-P[x1][y2]-P[x2][y1]+P[x1][y1]` 去算
整個二維前綴和實際的計算方法如下
```cpp=
// A: A[1][1] ~ A[n][m] 
for (int i=0;i<=n;i++)
    P[i][0] = 0 ;
for (int j=0;j<=m;j++)
    P[0][j] = 0 ;
for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
        P[i][j] = P[i-1][j] + P[i][j-1] + A[i][j] ;

// sum of x1, y1 -> x2, y2
cout << P[x2][y2]-P[x1][y2]-P[x2][y1]+P[x1][y1] << '\n' ;
```

二維差分的概念也一樣，我就不花時間去推了，直接給程式碼
```cpp=
// A: A[1][1] ~ A[n][m]
for (int i=0;i<=n;i++)
    D[i][0] = 0 ;
for (int j=0;j<=m;j++)
    D[0][j] = 0 ;
for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
        D[i][j] = A[i][j] - A[i-1][j] - A[i][j-1] + A[i-1][j-1] ;
```

## d206. 00108 - Maximum Sum
[題目連結](https://zerojudge.tw/ShowProblem?problemid=d206)
給你一個 $N\times N$ 的陣列，請你找出有最大和的子區域其和為多少
一個區域的和指的是該區域中所有元素值的和
一個區域是指相連的任意大小的子陣列

解析 :
二維的前綴和比較複雜，但是簡單來說就是取上取左然後刪除重疊區域
而子矩陣和也可以用前綴和算出，也是用刪上刪左補重疊的方式算出來
```cpp=
#include<bits/stdc++.h>
using namespace std ;

int main() {
    ios::sync_with_stdio(0), cin.tie(0) ;

    int n ;
    while (cin >> n) {

        vector<int> tmp(n+1, 0) ;
        vector<vector<int>> G(n+1, tmp) ;

        int ans = 0 ;
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=n;j++) {
                cin >> G[i][j] ;
                G[i][j] += G[i-1][j] + G[i][j-1] - G[i-1][j-1] ; // 前綴和

                // 子矩陣和
                for (int x=1;x<=i;x++)
                    for (int y=1;y<=j;y++)
                        ans = max(ans, G[i][j] - G[i][y-1] - G[x-1][j] + G[x-1][y-1]) ;
            }
        }
        cout << ans << '\n' ;
    }
    return 0 ;
}
```