一、概述
高维前缀和是一种解决多维度数据求和问题的算法,可以用于统计图像、地图等场景中的像素值、区域和等问题。该算法首次出现在1986年James Abello和Jeffrey S. Vitter两人的论文《Random Sampling from Multidimensional Distributions》中。基本思想是将每个维度的前缀和预处理出来,然后通过简单的计算获取任意矩形区域的和。
二、二维前缀和
首先,我们从二维前缀和开始介绍,以方便更好地理解高维前缀和。二维前缀和可以用于求解矩阵中某个矩形区域的和。定义矩阵 $M$ 的前缀和 $S$ 为:
S[i][j] = M[1][1] + M[1][2] + ... + M[1][j] + M[2][1] + M[2][2] + ... + M[2][j] + ... + M[i][1] + M[i][2] + ... + M[i][j]
显然,$S[i][j]$ 表示矩阵 $M$ 左上角到 $(i,j)$ 位置的所有元素之和。
求解任意矩形区域的和时,可以利用前缀和进行快速计算。假设要求解矩形 $(x1,y1)$ 到 $(x2,y2)$ 中所有元素的和,即 $\sum_{i=x1}^{x2}\sum_{j=y1}^{y2}M[i][j]$。通过前缀和,可以得到下面的计算公式:
sum = S[x2][y2] - S[x2][y1-1] - S[x1-1][y2] + S[x1-1][y1-1]
通过该公式,可以用 $O(1)$ 的时间复杂度求解任意矩形区域的和。
三、高维前缀和
对于 $n$ 维的数组而言,可以使用类似二维前缀和的方式进行求解。定义 $n$ 维数组 $A$ 的前缀和为:
S[p1][p2]...[pn] = A[1][1]...[1][p1][1]...[1][p2]...[1][pn] + A[1][1]...[1][p1][1]...[1][p2]...[2][pn] + ... + A[1][1]...[1][p1][2]...[m1][1]...[1][pn] + ... + A[1][n]...[m1][n][p1][1]...[1][p2]...[1][pn] + ... + A[m1][1]...[m1][n][p1][1]...[1][p2]...[1][pn] + ... + A[m1][m2]...[m_n][p1][1]...[1][p2]...[1][pn]
其中,$m_i$ 表示第 $i$ 维的长度,$p_i$ 表示在第 $i$ 维中要求和的最大下标。
类比二维前缀和,对于 $n$ 维数组 $A$,为了快速地计算任意多维矩形区域的和,可以预处理出 $n$ 维前缀和数组 $S$。对于 $n$ 维矩形区域 $R$,可以使用下面的公式进行计算:
sum = S[p1][p2]...[pn] - S[p1][p2]...[q_n-1][pn] - ... - S[p1][q_2-1]...[q_n-1][pn] + S[p1][q_2-1]...[q_n-1][q_n-1] - S[q_1-1][p2]...[q_n-1][pn] + S[q_1-1][q_2-1]...[q_n-1][pn] + ... + S[q_1-1][q_2-1]...[q_n-1][q_n-1]
其中,$p_i$ 表示在第 $i$ 维中要求和的最大下标,$q_i$ 表示在第 $i$ 维中要求和的最小下标。
四、代码示例
下面是使用 C++ 实现的二维前缀和和高维前缀和的代码示例。
二维前缀和:
int n, m; int a[MAX_N][MAX_M]; int s[MAX_N][MAX_M]; void precompute2D() { // 计算前缀和 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { s[i][j] = s[i][j-1] + s[i-1][j] - s[i-1][j-1] + a[i][j]; } } } int query2D(int x1, int y1, int x2, int y2) { return s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]; }
高维前缀和:
const int MAX_D = 10; const int MAX_SIZE = 1 << MAX_D; int n, m; int d[MAX_D]; // 每维的大小 int a[MAX_SIZE][MAX_SIZE][MAX_D]; int s[MAX_SIZE][MAX_SIZE][MAX_D]; void precomputeND() { // 计算前缀和 for (int k = 0; k < MAX_D; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { s[i][j][k] = s[i][j-1][k] + s[i-1][j][k] - s[i-1][j-1][k] + a[i][j][k]; } } } } int queryND(int *p, int *q) { int sum = 0; for (int k = 0; k < MAX_D; k++) { int p1 = p[k]+1, q1 = q[k]; for (int i = k+1; i < MAX_D; i++) { p1 *= d[i]; q1 *= d[i]; } int p2 = 1, q2 = 0; for (int i = 0; i < k; i++) { p2 *= d[i]; q2 *= d[i]; } sum += s[q1][q2][k] - s[q1][p2-1][k] - s[p1-1][q2][k] + s[p1-1][p2-1][k]; } return sum; }
原创文章,作者:SCIOC,如若转载,请注明出处:https://www.506064.com/n/349494.html