章节概述

差分是前缀和的逆运算。前缀和用于快速区间求和,差分用于快速区间修改
两者配合使用覆盖了大量数组操作的高效实现。

核心原理

1. 一维差分 — 区间修改 O(1)

给定数组 a[1..n],定义差分数组 d 满足: a 是 d 的前缀和,即:

等价于: (其中

区间修改公式:

对 a[l..r] 全部加上 x:
    d[l] += x
    d[r+1] -= x

修改完成后求一次前缀和即可得到新数组。修改 O(1),恢复 O(n)。

2. 二维差分 — 子矩阵修改 O(1)

对矩形 (x1,y1) 到 (x2,y2) 加上 x:

diff[x1][y1]     += x
diff[x1][y2+1]   -= x
diff[x2+1][y1]   -= x
diff[x2+1][y2+1] += x

原理: 前缀和传递,四个角通过容斥达到只对目标矩形内生效的效果。

3. IncDec 问题的数学优化

区间 ±1 操作等价于对差分数组两个端点操作。目标使 d[2..n] 全为 0:

  • 最少操作次数 = max(正数和, 负数绝对值之和)
  • 可能种数 = |正数和 - 负数绝对值之和| + 1

关键数据结构


一维差分

#include <iostream>
using namespace std;
const int N = 100005;
int d[N];
 
int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1, x; i <= n; i++) {
        cin >> x;
        d[i] += x;
        d[i + 1] -= x;
    }
    while (m--) {
        int l, r, x;
        cin >> l >> r >> x;
        d[l] += x;
        d[r + 1] -= x;
    }
    for (int i = 1; i <= n; i++) {
        d[i] += d[i - 1];
        cout << d[i] << " ";
    }
    return 0;
}

二维差分

#include <iostream>
using namespace std;
const int N = 1005;
int diff[N][N];
 
int main() {
    int n, m, q;
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++)
        for (int j = 1, x; j <= m; j++) {
            cin >> x;
            diff[i][j] += x;
            diff[i][j + 1] -= x;
            diff[i + 1][j] -= x;
            diff[i + 1][j + 1] += x;
        }
    while (q--) {
        int x1, y1, x2, y2, x;
        cin >> x1 >> y1 >> x2 >> y2 >> x;
        diff[x1][y1]     += x;
        diff[x1][y2 + 1] -= x;
        diff[x2 + 1][y1] -= x;
        diff[x2 + 1][y2 + 1] += x;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1];
            cout << diff[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

P3397 地毯

题目: 在 n×n 的网格上铺 m 块矩形地毯,每块地毯覆盖一个矩形区域。求每个格子被覆盖的次数。

思路: 二维差分的经典应用。对每块地毯做 O(1) 差分标记,最后求二维前缀和。

#include <iostream>
using namespace std;
int diff[1005][1005];
 
int main() {
    int n, m;
    cin >> n >> m;
    while (m--) {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        diff[x1][y1]++;
        diff[x1][y2 + 1]--;
        diff[x2 + 1][y1]--;
        diff[x2 + 1][y2 + 1]++;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1];
            cout << diff[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

P4552 [Poetize6] IncDec Sequence

题目: 给定长度为 n 的数列,每次可以选择一个区间 [l,r] 使区间内所有数 +1 或 -1。求最少操作次数使所有数相等,以及最终数列的可能种数。

思路: 问题转化为差分数组 d。设正数之和为 pos,负数绝对值之和为 neg。
最小操作次数 = max(pos, neg),可能种数 = |pos - neg| + 1。

#include <iostream>
#include <cmath>
using namespace std;
const int N = 100005;
long long a[N], d[N];
 
int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        d[i] = a[i] - a[i - 1];
    }
    long long pos = 0, neg = 0;
    for (int i = 2; i <= n; i++) {
        if (d[i] > 0) pos += d[i];
        else neg -= d[i];
    }
    cout << max(pos, neg) << endl;
    cout << abs(pos - neg) + 1 << endl;
    return 0;
}

推荐练习题(洛谷)


相关技巧


  • 前缀和: 差分的逆运算,配合使用覆盖区间求和与区间修改
  • 下标技巧: 差分数组的下标位移技巧
  • 贪心: IncDec Sequence 问题中的贪心配对思想

多平台练习

| 洛谷 | 本题单 | 竞赛基础 |
| POJ (北大) | PKU JudgeOnline | 经典题目,适合巩固 |
| HDU (杭电) | HDU OJ | 暑期多校训练 |
| Codeforces | Codeforces | 国际竞赛,适合提升 |