前綴和是一種重要的預處理,能大大降低查詢的時間復雜度。
最簡單的一道題就是給定 n 個數和 m 次詢問,每次詢問一段區間的和。求一個 O(n + m) 的做法。
用 O(n) 前綴和預處理,O(m) 詢問。
主要代碼
1 for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + a[i]; //O(n) 2 while(m--) //O(m) 3 { 4 int L, R; scanf("%d%d", &L, &R); 5 printf("%d\n", sum[R] - sum[L - 1]); 6 }
升級版
給定一個n*n的矩陣,找一個最大的子矩陣,使得這個子矩陣里面的元素和最大。
這道題最朴素的算法是 O(n ^ 6),用二維前綴和可以降到 O(n ^ 4)。
再想想前綴和矩陣怎么生成?看個圖就明白了:
那么最大的矩形前綴和就等於藍的矩陣加上綠的矩陣,再減去重疊面積,最后加上小方塊,即
sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + a[i][j]
主要代碼
1 for(int i = 1; i <= n; ++i) 2 for(int j = 1; j <= n; ++j) 3 sum[i][j] = sum[i - 1][j] + sum[i - 1][j] - sum[i - 1][j - 1] + a[i][j]; 4 for(int i = 1; i <= n; ++i) //枚舉矩陣左右端點 5 for(int j= 1; j <= n; ++j) 6 for(int k = i; K <= n; ++k) 7 for(int h = j; h <= n; ++h) 8 { 9 tot = sum[k][h] - sum[i - 1][h] - sum[k][j - 1] + sum[i - 1][j - 1]; //同理矩陣生成 10 ans = max(ans, tot); 11 }
不過這道題還有另一種方法,可達到 O(n ^ 3),不過思路就和二維前綴和有些差別了。
為了更好理解,先降維做道題。
給定一個數字序列,求最大區間和。
其實這道題使用貪心的思想。因為 sum[L, R] = sum[R] - sum[L - 1],所以只要在枚舉 R 的同時,一直取最小的 sum[L - 1],然后嘗試着更新 ans。
這個代碼實現有很多種方法,以下給出兩種,都很好理解
第一種
1 for(int R = 1; R <= n; ++R) //已知s[R],找最小的s[L-1] 2 { 3 MIN = min(MIN, s[R - 1]); //就是求 sum[L] 4 ans = max(ans, s[R] - MIN); 5 }
第二種
1 for(int i = 1; i <= n; ++i) 2 { 3 sum += a[i]; 4 if (sum < 0) sum = 0; //若小於0,就重新計數 5 ans = max(ans, sum); 6 }
掌握了這兩種方法,就可以解矩陣這道題了
只要枚舉矩陣的上下邊。
這里面的矩陣就可以延 i 到 j 的方向將維,再用上述思想更新答案
矩陣2減去矩陣1就得到矩陣3,然后將矩陣3降維。
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<cstring> 5 #include<algorithm> 6 using namespace std; 7 const int maxn = 4e2 + 5; 8 const int INF = 0x3f3f3f3f; 9 int n, ans = -INF; 10 int a[maxn][maxn], f[maxn][maxn]; //f[i][j]關於第j列到第i行的列前綴和 11 void init(const int& n) 12 { 13 for(int i = 1; i <= n; ++i) 14 for(int j = 1; j <= n; ++j) 15 f[i][j] = f[i - 1][j] + a[i][j]; //求一列的和 16 } 17 int b[maxn], sum[maxn]; //降維后的數組 18 int main() 19 { 20 scanf("%d", &n); 21 for(int i = 1; i <= n; ++i) 22 for(int j = 1; j <= n; ++j) 23 scanf("%d", &a[i][j]); 24 init(n); 25 for(int i = 1; i <= n; ++i) 26 for(int j = i; j <= n; ++j) 27 { 28 for(int k = 1; k <= n; ++k) b[k] = f[j][k] - f[i - 1][k]; //降維 29 for(int k = 1; k <= n; ++k) sum[k] = sum[k - 1] + b[k]; //求一維前綴和 30 int Min = INF; 31 for(int k = 1; k <= n; ++k) 32 { 33 Min = min(Min, sum[k - 1]); 34 ans = max(ans, sum[k] - Min); 35 } 36 } 37 printf("%d\n", ans); 38 return 0; 39 }
總而言之,前綴和雖然大多數用來預處理,但要是出關於它的題也能出的很難。