一、思路
一维差分:
核心作用
把区间 [l, r] 加 c 这种操作,从 O (n) 降到 O(1)。
核心公式
- 差分数组:
diff[i] = a[i] - a[i-1] - 区间加:
diff[l] += c,diff[r+1] -= c - 还原原数组:求前缀和
二维数组:
核心作用
子矩阵矩形区域统一加 c,O (1) 完成。
核心公式
diff[x1][y1] += c
diff[x1][y2+1] -= c
diff[x2+1][y1] -= c
diff[x2+1][y2+1] += c
最后用二维前缀和还原。
二、例题
1.语文成绩

思路:
- 初始有
n个学生成绩,需要执行p次「给第x到第y个学生每人加z分」的操作。 - 最后要输出全班的最低分。
分析:
题目的要求即区间修改 和 单点查询,可利用差分数组来处理区间修改,然后使用前缀和还原原数组,查找最小值。
代码实现:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e6 + 10;
int n, p, g[N], f[N], gmin = 100;
signed main()
{
cin >> n >> p;
for(int i = 1; i <= n; i++)
{
cin >> g[i];
f[i] = g[i] - g[i-1];
}
while(p--)
{
int l, r, x;
cin >> l >> r >> x;
f[l] += x, f[r+1] -= x;
}
for(int i = 1; i <= n; i++)
{
g[i] = g[i-1] + f[i];
gmin = min(gmin, g[i]);
}
cout << gmin << endl;
return 0;
}
2.海底高铁

思路:
依次经过若干城市,每段相邻城市之间的铁路会被多次经过 ,我们需要先统计出每段铁路被经过多少次。
分析:
从城市 x 走到 y,等价于对区间 [x, y−1] 的铁路都经过一次。
代码实现:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
int n, m, f[N], a, b, c, ret;
int x, y;
signed main ()
{
cin >> n >> m;
cin >> x;
for(int i = 2; i <= m; i++)
{
cin >> y;
if(x < y) f[x]++, f[y]--;
if(x > y) f[y]++, f[x]--;
x = y;
}
for(int i = 1; i <= n; i++) f[i] += f[i-1];
for(int i = 1; i < n; i++)
{
cin >> a >> b >> c;
ret += min(a * f[i], c + f[i] * b);
}
cout << ret << endl;
return 0;
}
3.借教室
P1083 [NOIP 2012 提高组] 借教室 - 洛谷

思路:
按顺序处理若干借教室订单,每个订单需要在第 s 天到第 t 天每天借用 d 间教室。若某一天所需教室超过现有数量,则该订单无法满足,要求找到第一个无法满足的订单编号
分析:
订单越多,每天的教室需求只会增加不会减少,因此满足情况呈现单调性:前 k 个可满足 → 所有更小编号都可满足;前 k 个不可满足 → 所有更大编号都不可满足。可用二分答案定位第一个不满足的订单。
对二分出的前 mid 个订单,用差分快速统计每天教室总需求,前缀和还原得到每天实际需求,判断是否存在某天超过可用教室数。
代码实现:
cpp
// 暴力
#define int long long
const int N = 1e6 + 10;
int n, m, r[N], f[N];
signed main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> r[i];
// 初始化差分数组
for (int i = 1; i <= n; i++) f[i] = r[i] - r[i-1];
f[n+1] = -r[n];
for (int k = 1; k <= m; k++) {
int d, s, t;
cin >> d >> s >> t;
// 差分标记这个订单
f[s] -= d;
f[t+1] += d;
// 前缀和检查是否有天不够
int now = 0;
bool ok = true;
for (int i = 1; i <= n; i++) {
now += f[i];
if (now < 0) {
cout << -1 << endl << k << endl;
return 0;
}
}
}
cout << 0 << endl;
return 0;
}
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m;
int r[N], d[N], s[N], t[N];
long long diff[N]; // 用long long防止溢出
// 检查前k个订单是否可行
bool check(int k) {
memset(diff, 0, sizeof(diff));
// 差分标记前k个订单
for (int i = 1; i <= k; i++) {
diff[s[i]] += d[i];
diff[t[i] + 1] -= d[i];
}
// 前缀和还原,判断是否超量
long long now = 0;
for (int i = 1; i <= n; i++) {
now += diff[i];
if (now > r[i]) return false;
}
return true;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> r[i];
for (int i = 1; i <= m; i++) cin >> d[i] >> s[i] >> t[i];
// 二分找第一个不可行的订单
int l = 1, r = m, ans = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
if (ans == m) cout << 0 << endl;
else {
cout << -1 << endl;
cout << ans + 1 << endl;
}
return 0;
}
4.地毯

代码实现:
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N];
int main ()
{
int m, n; cin >> n >> m;
while(m--)
{
int x1, x2, y1, y2; cin >> x1 >> y1 >> x2 >> y2;
f[x1][y1] ++;
f[x1][y2 + 1] --;
f[x2 + 1][y1] --;
f[x2 + 1][y2 + 1] ++;
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
f[i][j] += f[i-1][j] + f[i][j-1] - f[i-1][j-1];
cout << f[i][j] << " ";
}
cout << endl;
}
return 0;
}