【模板】二维差分

思路

这题是二维前缀和的"逆操作"。二维前缀和解决的是多次查询子矩阵和,而二维差分解决的是多次给子矩阵加值

先想暴力:每次操作都遍历子矩阵把每个元素加上 c,单次操作 ,q 次就是 ,肯定超时。

回忆一维差分是怎么做的:给区间 [l, r]c,只需要 d[l] += c, d[r+1] -= c,最后对差分数组求前缀和就能还原。核心思想是修改 O(1),最后统一还原

二维也一样。我们构造一个差分数组 d[i][j],使得对 d 做二维前缀和之后能还原出原始矩阵 a

怎么从原矩阵构造差分数组?

既然二维前缀和的公式是:

$$

那反过来,差分数组就是:

$$

怎么对子矩阵 (r1,c1)(r2,c2)c

和一维差分类似,用容斥在四个角打标记:

$$

$$

$$

$$

画个图理解:我们在左上角加 c,但这会影响到右边和下面不该加的区域,所以在右边和下面各减掉,而右下角被减了两次要加回来——还是容斥原理

所有操作打完标记后,对 d 做一次二维前缀和,就得到最终矩阵了。

整个流程:构造差分 + 打标记 + 还原 ,非常高效。

代码

#include <cstdio>
using namespace std;

static long long a[1005][1005], d[1005][1005];

int main(){
    int n, m, q;
    scanf("%d%d%d", &n, &m, &q);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            scanf("%lld", &a[i][j]);

    // 构造二维差分数组
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            d[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1];

    // 处理每次操作,在差分数组上打标记
    while(q--){
        int r1, c1, r2, c2;
        long long c;
        scanf("%d%d%d%d%lld", &r1, &c1, &r2, &c2, &c);
        d[r1][c1] += c;
        d[r2+1][c1] -= c;
        d[r1][c2+1] -= c;
        d[r2+1][c2+1] += c;
    }

    // 对差分数组求二维前缀和,还原最终矩阵
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            d[i][j] += d[i-1][j] + d[i][j-1] - d[i-1][j-1];

    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            if(j > 1) putchar(' ');
            printf("%lld", d[i][j]);
        }
        putchar('\n');
    }
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StreamTokenizer st = new StreamTokenizer(br);
        StringBuilder sb = new StringBuilder();

        st.nextToken(); int n = (int)st.nval;
        st.nextToken(); int m = (int)st.nval;
        st.nextToken(); int q = (int)st.nval;

        long[][] a = new long[n+2][m+2];
        long[][] d = new long[n+2][m+2];

        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++){
                st.nextToken();
                a[i][j] = (long)st.nval;
            }

        // 构造二维差分数组
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
                d[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1];

        // 处理每次操作
        for(int k = 0; k < q; k++){
            st.nextToken(); int r1 = (int)st.nval;
            st.nextToken(); int c1 = (int)st.nval;
            st.nextToken(); int r2 = (int)st.nval;
            st.nextToken(); int c2 = (int)st.nval;
            st.nextToken(); long c = (long)st.nval;
            d[r1][c1] += c;
            d[r2+1][c1] -= c;
            d[r1][c2+1] -= c;
            d[r2+1][c2+1] += c;
        }

        // 二维前缀和还原
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
                d[i][j] += d[i-1][j] + d[i][j-1] - d[i-1][j-1];

        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if(j > 1) sb.append(' ');
                sb.append(d[i][j]);
            }
            sb.append('\n');
        }
        System.out.print(sb);
    }
}
import sys
input = sys.stdin.readline

def main():
    n, m, q = map(int, input().split())
    a = [[0]*(m+2) for _ in range(n+2)]
    d = [[0]*(m+2) for _ in range(n+2)]
    for i in range(1, n+1):
        row = list(map(int, input().split()))
        for j in range(1, m+1):
            a[i][j] = row[j-1]
    for i in range(1, n+1):
        for j in range(1, m+1):
            d[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]
    for _ in range(q):
        parts = list(map(int, input().split()))
        r1, c1, r2, c2, c = parts
        d[r1][c1] += c
        d[r2+1][c1] -= c
        d[r1][c2+1] -= c
        d[r2+1][c2+1] += c
    for i in range(1, n+1):
        for j in range(1, m+1):
            d[i][j] += d[i-1][j] + d[i][j-1] - d[i-1][j-1]
    out = []
    for i in range(1, n+1):
        out.append(' '.join(str(d[i][j]) for j in range(1, m+1)))
    sys.stdout.write('\n'.join(out) + '\n')

main()
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', line => lines.push(line));
rl.on('close', () => {
    let idx = 0;
    const [n, m, q] = lines[idx++].split(' ').map(Number);
    const a = Array.from({length: n+2}, () => new Array(m+2).fill(0));
    const d = Array.from({length: n+2}, () => new Array(m+2).fill(0));
    for (let i = 1; i <= n; i++) {
        const row = lines[idx++].split(' ').map(Number);
        for (let j = 1; j <= m; j++) {
            a[i][j] = row[j-1];
        }
    }
    for (let i = 1; i <= n; i++)
        for (let j = 1; j <= m; j++)
            d[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1];
    for (let k = 0; k < q; k++) {
        const parts = lines[idx++].split(' ').map(Number);
        const [r1, c1, r2, c2, c] = parts;
        d[r1][c1] += c;
        d[r2+1][c1] -= c;
        d[r1][c2+1] -= c;
        d[r2+1][c2+1] += c;
    }
    for (let i = 1; i <= n; i++)
        for (let j = 1; j <= m; j++)
            d[i][j] += d[i-1][j] + d[i][j-1] - d[i-1][j-1];
    const out = [];
    for (let i = 1; i <= n; i++) {
        const row = [];
        for (let j = 1; j <= m; j++) row.push(d[i][j]);
        out.push(row.join(' '));
    }
    console.log(out.join('\n'));
});

复杂度

  • 时间复杂度:。构造差分和还原各遍历一次矩阵,每次操作
  • 空间复杂度:。存储差分数组。