对于长度为 n 的序列 {ai},如果要多次查询区间 [l,r] 中序列数字的和,就可以考虑使用前缀和。序列的前缀和就是
Si=j=1∑iai.
它可以通过递推关系式
S0=0,Si=Si−1+ai
逐项计算得到。要询问区间 [l,r] 内的序列的和,只需要计算差值
S([l,r])=Sr−Sl−1.
就这样,通过 O(n) 时间预处理,能够将单次查询区间和的复杂度降低到 O(1)。
参考实现
[list2tab]
C++
int n; // Array size.std::vector<int> a; // Array. (indexed from 1)std::vector<int> ps; // Prefix sum array.// Calculate prefix sum.void prefix_sum() { ps = a; // Or simply: // std::partial_sum(a.begin(), a.end(), ps.begin()); for (int i = 1; i <= n; ++i) { ps[i] += ps[i - 1]; }}// Query sum of elements in [l, r].int query(int l, int r) { return ps[r] - ps[l - 1]; }
Python
n = 0 # Array sizea = [] # Array (indexed from 1)ps = [] # Prefix sum array# Calculate prefix sumdef prefix_sum(): global ps ps = a[:] # Or simply: # ps = list(itertools.accumulate(a)) for i in range(1, n + 1): ps[i] += ps[i - 1]# Query sum of elements in [l, r]def query(l, r): return ps[r] - ps[l - 1]
#include <algorithm>#include <iostream>#include <vector>// --8<-- [start:core]int n, m;std::vector<std::vector<int>> a, ps; // (n + 1) x (m + 1).// Calculate the prefix sum of 2-d array.void prefix_sum() { ps = a; for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) ps[i][j] += ps[i - 1][j] + ps[i][j - 1] - ps[i - 1][j - 1];}// Find the sum of elements in submatrix [x1, y1] to [x2, y2].int query(int x1, int y1, int x2, int y2) { return ps[x2][y2] - ps[x1 - 1][y2] - ps[x2][y1 - 1] + ps[x1 - 1][y1 - 1];}// --8<-- [end:core]int main() { std::cin >> n >> m; a.assign(n + 1, std::vector<int>(m + 1)); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) std::cin >> a[i][j]; prefix_sum(); int ans = 0; for (int l = 1; l <= std::min(n, m); ++l) for (int i = l; i <= n; i++) for (int j = l; j <= m; j++) if (query(i - l + 1, j - l + 1, i, j) == l * l) ans = std::max(ans, l); std::cout << ans << std::endl; return 0;}
Python
# --8<-- [start:core]n, m = 0, 0a = [] # (n+1) x (m+1)ps = [] # prefix sum array# Calculate the prefix sum of 2D array.def prefix_sum(): global ps ps = [row[:] for row in a] # Deep copy of a for i in range(1, n + 1): for j in range(1, m + 1): ps[i][j] += ps[i - 1][j] + ps[i][j - 1] - ps[i - 1][j - 1]# Find the sum of elements in submatrix [x1, y1] to [x2, y2].def query(x1, y1, x2, y2): return ps[x2][y2] - ps[x1 - 1][y2] - ps[x2][y1 - 1] + ps[x1 - 1][y1 - 1]# --8<-- [end:core]if __name__ == "__main__": n, m = map(int, input().split()) # Initialize with zero padding for 1-based indexing a = [[0] * (m + 1)] for _ in range(n): row = list(map(int, input().split())) a.append([0] + row) prefix_sum() ans = 0 for l in range(1, min(n, m) + 1): for i in range(l, n + 1): for j in range(l, m + 1): if query(i - l + 1, j - l + 1, i, j) == l * l: ans = max(ans, l) print(ans)
逐维前缀和
对于一般的情形,给定 k 维数组 A,大小为 N,同样要求得其前缀和 S。这里,
Si1,⋯,ik=i1′≤i1∑⋯ik′≤ik∑Ai1′,⋯,ik′.
从上式可以看出,k 维前缀和就等于 k 次求和。所以,一个显然的算法是,每次只考虑一个维度,固定所有其它维度,然后求若干个一维前缀和,这样对所有 k 个维度分别求和之后,得到的就是 k 维前缀和。
三维前缀和的参考实现
int N1, N2, N3;std::vector<std::vector<std::vector<int>>> a, ps; // (N1 + 1) x (N2 + 1) x (N3 + 1).// Calculate prefix sum of 3d array.void prefix_sum() { ps = a; // Prefix-sum for 3rd dimension. for (int i = 1; i <= N1; ++i) for (int j = 1; j <= N2; ++j) for (int k = 1; k <= N3; ++k) ps[i][j][k] += ps[i][j][k - 1]; // Prefix-sum for 2nd dimension. for (int i = 1; i <= N1; ++i) for (int j = 1; j <= N2; ++j) for (int k = 1; k <= N3; ++k) ps[i][j][k] += ps[i][j - 1][k]; // Prefix-sum for 1st dimension. for (int i = 1; i <= N1; ++i) for (int j = 1; j <= N2; ++j) for (int k = 1; k <= N3; ++k) ps[i][j][k] += ps[i - 1][j][k];}
int n;std::vector<int> diff, a;// Add v to each element in [l, r].void add(int l, int r, int v) { diff[l] += v; if (r < n) diff[r + 1] -= v;}// Execute this after all modifications and before all queries.void prefix_sum() { for (int i = 1; i <= n; ++i) a[i] = a[i - 1] + diff[i];}
int n, m;std::vector<std::vector<int>> diff, a;// Add v to each element from [x1, y1] to [x2, y2].void add(int x1, int y1, int x2, int y2, int v) { diff[x1][y1] += v; if (x2 < n) diff[x2 + 1][y1] -= v; if (y2 < m) diff[x1][y2 + 1] -= v; if (x2 < n && y2 < m) diff[x2 + 1][y2 + 1] += v;}// Execute this after all modifications and before all queries.void prefix_sum() { a = diff; for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) a[i][j] += a[i - 1][j]; for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) a[i][j] += a[i][j - 1];}
当然,类似的想法对于维度 k>2 也成立,但是单次修改操作需要的时间复杂度为 O(2k),随着 k 增大而不再实用。