NeFut Logo NeFut
EN 管理员登录

高效区间更新与恢复:差分数组的深度解析

发布于:2026-05-27 09:17 最后更新:2026-06-06 13:04
#algorithm #Dynamic Programming #Data Structure

核心逻辑与数学原理

差分是前缀和的逆运算,其物理本质是将全局区间修改转化为局部端点修正,实现对区间操作的降维打击。

设原序列为 $A$,差分序列为 $D$。两者满足以下代数关系:

$$D[i] = \begin{cases} A[i] & i = 1 \\ A[i] - A[i-1] & i > 1 \end{cases}$$

由定义可知,原序列 $A$ 实际上是差分序列 $D$ 的前缀和序列:

$$A[i] = \sum_{k=1}^{i} D[k]$$

当需要对原序列的区间 $[l, r]$ 全体加上增量 $v$ 时,若直接暴力修改,时间复杂度为 $O(N)$。观察差分结构的内部性质:对于 $l < i \le r$ 的任意位置,由于 $A[i]$ 与 $A[i-1]$ 同时加上了 $v$,其相对差值 $A[i] - A[i-1]$ 保持不变,即 $D[i]$ 无需任何改动。真正发生跃变的只有两个边界:

  1. $A[l]$ 比 $A[l-1]$ 多增了 $v$,故 $D[l]$ 增加 $v$。
  2. $A[r+1]$ 比 $A[r]$ 相对少增了 $v$,故 $D[r+1]$ 减少 $v$。

通过将区间问题退化为两点修改,单次操作复杂度被压制到 $O(1)$。


算法推导

1. 一维差分维护与还原

对于 $M$ 次独立的区间修改 $(l, r, v)$,直接在差分数组 $D$ 上进行:

$$D[l] \leftarrow D[l] + v$$

$$D[r+1] \leftarrow D[r+1] - v$$

修改完毕后,利用前缀和递推关系公式 $A[i] = A[i-1] + D[i]$,可在 $O(N)$ 时间内离线还原出最终的整个原序列 $A$。

2. 二维矩阵差分推导

在二维矩阵中,若要对以 $(x_1, y_1)$ 为左上角、$(x_2, y_2)$ 为右下角的子矩阵全加上 $v$,利用二维容斥原理,我们在二维差分矩阵 $D$ 上的两点修正演变为四点修正。根据二维前缀和的逆向拓扑结构,修改方程为:

$$D[x_1][y_1] \leftarrow D[x_1][y_1] + v$$

$$D[x_2+1][y_1] \leftarrow D[x_2+1][y_1] - v$$

$$D[x_1][y_2+1] \leftarrow D[x_1][y_2+1] - v$$

$$D[x_2+1][y_2+1] \leftarrow D[x_2+1][y_2+1] + v$$

恢复原矩阵时,利用二维前缀和状态转移方程进行 $O(N \times M)$ 的状态扫描。


C++ 标准源码

#include <iostream>
#include <vector>

using namespace std;

// 演示二维差分矩阵的修改与还原过程
void solveMatrixDifference() {
    int n, m, p;
    if (!(cin >> n >> m >> p)) return;

    // 1-indexed 边界,防止 x2+1 或 y2+1 发生越界崩溃,多开 2 行 2 列作为安全缓冲
    vector<vector<long long>> d(n + 2, vector<long long>(m + 2, 0));

    // P 次区间增量修改
    for (int i = 0; i < p; ++i) {
        int x1, y1, x2, y2;
        long long v;
        cin >> x1 >> y1 >> x2 >> y2 >> v;

        // 关键点:基于容斥原理的 4 点修正,错一个符号直接爆 0
        d[x1][y1] += v;
        d[x2 + 1][y1] -= v;
        d[x1][y2 + 1] -= v;
        d[x2 + 1][y2 + 1] += v;
    }

    // O(N*M) 前缀和还原矩阵
    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] + d[i][j];
            cout << d[i][j] << (j == m ? "" : " ");
        }
        cout << "\n";
    }
}

int main() {
    // 斩断标准流同步常数
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    solveMatrixDifference();

    return 0;
}

NOIP 实战避坑指南

  1. 右端点加一($r+1$)造成的内存越界访问
    差分修正时必然涉及 $D[r+1]$ 或二维的 $D[x_2+1][y_2+1]$。当输入的修改边界恰好等于数据范围上限 $N$ 时,$r+1$ 会直接指向 $N+1$。如果数组刚好只开了 $N$ 大小,会触发段错误(Segmentation Fault)或者极其隐蔽的内存邻接越界写,导致其他变量值被诡异覆盖。必须养成习惯:差分数组的空间至少开到 $N + 2$
  2. 前缀和还原时的累加溢出
    即使单次修改增量 $v$ 较小,如果修改操作 $M$ 达到 $10^5$ 级别,频繁区间叠加后的最终原元素值极易冲破 int 的 $2 \times 10^9$ 上限。如果图省事将差分数组定义为 int,在前缀和累加阶段就会发生整型溢出,导致还原出的数据变成负数。所有的差分数组和还原变量一律使用 long long 存储

经典 NOIP/洛谷 真题

1. 洛谷 P3397 地毯

2. 洛谷 P1083 [NOIP2012 提高组] 借教室

原文链接: local://2.2

[h] 返回首页