算法:前缀和、差分

一、二维前缀和(n+1)

1.求 pre[i][j](从 (1,1)(i,j) 的矩形和)

|---|---|-----------|---------|---|
| | 1 | 2 | 3 | 4 |
| 1 |   |   |   |   |
| 2 |   |   |   |   |
| 3 |   | (i-1,j-1) | (i-1,j) |   |
| 4 |   | (i,j-1) | (i,j) |   |
| 5 |   |   |   |   |

preij = prei-1j + preij-1 - prei-1j-1 + aij;

2. 求任意子矩阵 (x1,y1)(x2,y2) 的和

|-------|---|-------------|---------|---|-----------|
| | 1 | 2 | 3 | 4 | 5 |
| 1 |   |   |   |   |   |
| 2 |   | (x1-1,y1-1) |   |   | (x1-1,y2) |
| 3 |   |   | (x1,y1) |   |   |
| 4 |   |   |   |   |   |
| 5 |   | (x2,y1-1) |   |   | (x2,y2) |
| 6 |   |   |   |   |   |
| 7 |   |   |   |   |   |

sum = prex2y2 - prex1-1y2 - prex2y1-1 + prex1-1y1-1;

二、二维差分(n+2)

1. 核心关系

对 diff 做二维前缀和 = 原数组 a

也就是说:

aij = ai-1j + aij-1 - ai-1j-1 + diffij;


2. 怎么求 diff(从 a 构造)

diffij = aij - ai-1j - aij-1 + ai-1j-1;

和前缀和公式长得一样,但方向相反(减号变多了)。


3. 区间修改:让 (x1,y1)(x2,y2) 每个数 + val

|------|---|---|-----------|---|---------|-------------|
| diff | 1 | 2 | 3 | 4 | 5 | n+2 |
| 1 |   |   |   |   |   | |
| 2 |   |   |   |   |   | |
| 3 |   |   | (x1,y1) |   |   | (x1,y2+1) |
| 4 |   |   |   |   |   |   |
| 5 |   |   |   |   | (x2,y2) |   |
| n+2 |   |   | (x2+1,y1) |   |   | (x2+1,y2+1) |

改 4 个角:

复制代码
diff[x1][y1] += val;
diff[x1][y2 + 1] -= val;
diff[x2 + 1][y1] -= val;
diff[x2 + 1][y2 + 1] += val;

记忆口诀:左上 +,右上 -,左下 -,右下 +


4. 完整流程

java 复制代码
// 1. 从 a 构造 diff
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        diff[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1];
    }
}

// 2. 多次区间修改(改 4 个角)
diff[x1][y1] += val;
diff[x1][y2 + 1] -= val;
diff[x2 + 1][y1] -= val;
diff[x2 + 1][y2 + 1] += val;

// 3. 对 diff 做二维前缀和,还原成 a
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + diff[i][j];
    }
}