二维前缀和与二维差分
二维前缀和:快速求一个子矩形的元素和。
二维差分:快速给一个子矩形整体加上一个数。
前缀和与差分互为逆运算
二维前缀和
如何计算二维前缀和
我们设\(s[i][j]\) \(=\) 从 \((1,1)\) 到 \((i,j)\) 这个矩形的元素总和
也就是下面这一部分的阴影面积
\[\begin{array}{ll}
(1,1) \\[-2ex]
& \begin{bmatrix}
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix} \\[-2ex]
& \hspace{3cm} (i,j)
\end{array}
\]
我们可以观察到\(s[i - 1][j]\)和\(s[i][j - 1]\)如下
\[\begin{array}{ll}
{\color{blue}s[i-1][j]}: \; (1,1) \\[-2ex]
& \begin{bmatrix}
\color{blue}\blacksquare & \color{blue}\blacksquare & \color{blue}\blacksquare & \color{blue}\blacksquare \\
\color{blue}\blacksquare & \color{blue}\blacksquare & \color{blue}\blacksquare & \color{blue}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix} \\[-2ex]
& \hspace{3cm} (i,j)
\end{array}
\]
\[\begin{array}{ll}
{\color{orange}s[i][j-1]}: \; (1,1) \\[-2ex]
& \begin{bmatrix}
\color{orange}\blacksquare & \color{orange}\blacksquare & \color{orange}\blacksquare & \color{black}\blacksquare \\
\color{orange}\blacksquare & \color{orange}\blacksquare & \color{orange}\blacksquare & \color{black}\blacksquare \\
\color{orange}\blacksquare & \color{orange}\blacksquare & \color{orange}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix} \\[-2ex]
& \hspace{3cm} (i,j)
\end{array}
\]
由于蓝色和橙色重叠的部分(左上角)被计算了两次,所以要减去一次\(s[i-1][j-1]\),再加上当前格子\(a[i][j]\):
\[s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]
\]
如何利用二维前缀和求任意的矩形面积
现在我们想求从\((x_i, y_i)\)为左上角到\((x_j, y_j)\)为右下角的矩形面积
记这个子矩形的元素和为\(sum\),利用容斥可以得到:
\[sum = s[x_j][y_j] - s[x_i-1][y_j] - s[x_j][y_i-1] + s[x_i-1][y_i-1]
\]
下面用同一个例子\((x_i,y_i)=(2,2)\)、\((x_j,y_j)=(3,4)\)来图解:
\[\begin{array}{ll}
{\color{green}s[3][4]}: \; (1,1) \\[-2ex]
& \begin{bmatrix}
\color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare \\
\color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare \\
\color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix} \\[-2ex]
& \hspace{3cm} (3,4)
\end{array}
\]
\[\begin{array}{ll}
{\color{blue}s[1][4]}: \; (1,1) \\[-2ex]
& \begin{bmatrix}
\color{blue}\blacksquare & \color{blue}\blacksquare & \color{blue}\blacksquare & \color{blue}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix}
\end{array}
\]
\[\begin{array}{ll}
{\color{orange}s[3][1]}: \; (1,1) \\[-2ex]
& \begin{bmatrix}
\color{orange}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{orange}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{orange}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix}
\end{array}
\]
\[\begin{array}{ll}
{\color{red}s[1][1]}: \; (1,1) \\[-2ex]
& \begin{bmatrix}
\color{red}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix}
\end{array}
\]
目标子矩形就是第\(2\sim3\)行、第\(2\sim4\)列:
\[\begin{bmatrix}
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{purple}\blacksquare & \color{purple}\blacksquare & \color{purple}\blacksquare \\
\color{black}\blacksquare & \color{purple}\blacksquare & \color{purple}\blacksquare & \color{purple}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix}
\]
含义分别是:
\[\begin{aligned}
&s[x_j][y_j] &&\text{大矩形 }(1,1)\to(x_j,y_j)\\
&-\;s[x_i-1][y_j] &&\text{减去上方多余部分}\\
&-\;s[x_j][y_i-1] &&\text{减去左侧多余部分}\\
&+\;s[x_i-1][y_i-1] &&\text{左上角被减了两次,要加回来}
\end{aligned}
\]
tips:如果你把前缀和数组开成\((n+1)\times(m+1)\),并让第\(0\)行、第\(0\)列都为\(0\),那么上式在边界时也可以直接使用,不需要额外分类讨论。
例如求\((2,2)\)到\((3,4)\)的子矩形和:
\[sum = s[3][4] - s[1][4] - s[3][1] + s[1][1]
\]
二维前缀和模板
void solve()
{
int n = 0, m = 0;
std::cin >> n >> m;
std::vector<std::vector<int>> mat(n + 1, std::vector<int>(m + 1, 0));
std::vector<std::vector<int>> pre(n + 1, std::vector<int>(m + 1, 0));
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) std::cin >> mat[i][j];
// 求二维前缀和
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + mat[i][j];
}
}
// 求(x1, y1)到(x2, y2)的子矩阵的和
int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
std::cin >> x1 >> y1; std::cin >> x2 >> y2;
int res = pre[x2][y2] - pre[x2][y1 - 1] - pre[x1 - 1][y2] + pre[x1 - 1][y1 - 1];
}
二维差分
如何定义二维差分
设原数组为\(a[i][j]\),差分数组为\(d[i][j]\)。
它们的关系和二维前缀和是完全反过来的:
\[a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + d[i][j]
\]
所以也可以从\(a\)反推\(d\):
\[d[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]
\]
如何给任意子矩形整体加上\(k\)
如果要把子矩形\((x_1,y_1)\to(x_2,y_2)\)内所有元素都加上\(k\),只需要改动\(d\)中的四个点:
\[\begin{aligned}
d[x_1][y_1] &+= k \\
d[x_2+1][y_1] &-= k \\
d[x_1][y_2+1] &-= k \\
d[x_2+1][y_2+1] &+= k
\end{aligned}
\]
下面用同一个例子\((x_1,y_1)=(2,2)\)、\((x_2,y_2)=(3,4)\)图解四个更新点:
\[\begin{bmatrix}
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{green}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{orange}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{blue}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{red}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix}
\]
其中:
\[\begin{aligned}
&\color{green}{(2,2)}: +k \\
&\color{blue}{(4,2)}: -k \\
&\color{orange}{(2,5)}: -k \\
&\color{red}{(4,5)}: +k
\end{aligned}
\]
为了更直观,下面看四个点在“还原成\(a\)之后”各自影响的区域。
先只看\(\color{green}{(2,2):+k}\),它会让右下角整片都\(+k\):
\[\begin{bmatrix}
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare \\
\color{black}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare \\
\color{black}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare \\
\color{black}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare \\
\end{bmatrix}
\]
再加上\(\color{blue}{(4,2):-k}\),会把第\(4\)行及以下、且第\(2\)列及右侧抵消掉:
\[\begin{bmatrix}
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare \\
\color{black}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare & \color{green}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix}
\]
再加上\(\color{orange}{(2,5):-k}\),会把第\(5\)列及右侧、且第\(2\)行及以下抵消掉:
\[\begin{bmatrix}
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{purple}\blacksquare & \color{purple}\blacksquare & \color{purple}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{purple}\blacksquare & \color{purple}\blacksquare & \color{purple}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare & \color{black}\blacksquare \\
\end{bmatrix}
\]
最后\(\color{red}{(4,5):+k}\)是“补偿点”,把前两次减法在右下角交叉区域多减的一次加回来,最终只剩目标子矩形\((2,2)\to(3,4)\)整体\(+k\)。
把所有操作都累加到\(d\)后,最后做一次二维前缀和,就能把每次矩形加法“还原”到\(a\)上。
如何从二维差分还原原数组
对\(d\)做二维前缀和即可:
\[a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + d[i][j]
\]
实现时通常把数组开成\((n+2)\times(m+2)\),这样更新到\(x_2+1\)、\(y_2+1\)时不会越界,也不需要写很多边界判断。
完整流程:
- 初始化\(d\)为全\(0\)。
- 每次矩形加\(k\)时,按四点更新\(d\)。
- 最后对\(d\)做二维前缀和,得到最终\(a\)。
二维差分模板
void solve()
{
int n = 0, m = 0;
std::cin >> n >> m;
std::vector<std::vector<int>> mat(n + 1, std::vector<int>(m + 1, 0));
std::vector<std::vector<int>> dif(n + 1, std::vector<int>(m + 1, 0));
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) std::cin >> mat[i][j];
// 求二维差分
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
dif[i][j] = mat[i][j] - mat[i - 1][j] - mat[i][j - 1] + mat[i - 1][j - 1];
}
}
// 修改: (x1, y1)到(x2, y2)加上k
int x1, y1, x2, y2, k;
std::cin >> x1 >> y1 >> x2 >> y2 >> k;
dif[x1][y1] += k;
dif[x2 + 1][y1] -= k;
dif[x1][y2 + 1] -= k;
dif[x2 + 1][y2 + 1] += k;
}