Title

(模板)二维前缀和与二维差分

二维前缀和与二维差分

二维前缀和:快速求一个子矩形的元素和。

二维差分:快速给一个子矩形整体加上一个数。

前缀和与差分互为逆运算

二维前缀和

如何计算二维前缀和

我们设\(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\)时不会越界,也不需要写很多边界判断。

完整流程:

  1. 初始化\(d\)为全\(0\)
  2. 每次矩形加\(k\)时,按四点更新\(d\)
  3. 最后对\(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;

}
posted @ 2026-03-20 15:29  栗悟饭与龟功気波  阅读(8)  评论(0)    收藏  举报