题解:AcWing 797 差分
【题目来源】
AcWing:797 差分 - AcWing题库
【题目描述】
输入一个长度为 \(n\) 的整数序列。
接下来输入 \(m\) 个操作,每个操作包含三个整数 \(l,r,c\),表示将序列中 \([l,r]\) 之间的每个数加上 \(c\)。
请你输出进行完所有操作后的序列。
【输入】
第一行包含两个整数 \(n\) 和 \(m\)。
第二行包含 \(n\) 个整数,表示整数序列。
接下来 \(m\) 行,每行包含三个整数 \(l,r,c\),表示一个操作。
【输出】
共一行,包含 \(n\) 个整数,表示最终序列。
【输入样例】
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
【输出样例】
3 4 5 3 4 2
【核心思想】
-
问题分析:给定长度为 \(n\) 的序列 \(a[1..n]\),进行 \(m\) 次区间加操作,每次将 \([l, r]\) 内每个元素加上 \(c\)。若对每个操作暴力遍历区间修改,时间复杂度为 \(O(m \cdot n)\)。关键观察是:差分数组可以将区间加法转化为 \(O(1)\) 的单点更新,最后通过一次前缀和还原得到最终结果。
-
算法选择:
- 差分数组(Difference Array):定义 \(b[i] = a[i] - a[i-1]\)(\(a[0] = 0\)),则区间 \([l, r]\) 加 \(c\) 等价于 \(b[l] += c\) 和 \(b[r+1] -= c\)
- 前缀和还原:所有操作完成后,对差分数组求前缀和即可得到最终数组
-
关键步骤:
- 初始化差分数组:
- 方式一:\(b[i] = a[i] - a[i-1]\)(直接计算)
- 方式二:对每个位置 \(i\) 执行
insert(i, i, a[i])(逐点插入)
- 区间更新操作
insert(l, r, c):- \(b[l] \leftarrow b[l] + c\):从位置 \(l\) 开始,后续所有前缀和都增加 \(c\)
- \(b[r+1] \leftarrow b[r+1] - c\):从位置 \(r+1\) 开始,抵消增加的影响,确保只有 \([l, r]\) 区间内的元素实际增加 \(c\)
- 前缀和还原:
- 对 \(i\) 从 \(1\) 到 \(n\):\(b[i] \leftarrow b[i] + b[i-1]\)
- 此时 \(b[i]\) 即为最终数组的第 \(i\) 个元素
- 初始化差分数组:
-
时间/空间复杂度:
- 时间复杂度:\(O(n + m)\),初始化差分数组 \(O(n)\),\(m\) 次操作每次 \(O(1)\),前缀和还原 \(O(n)\)
- 空间复杂度:\(O(n)\),存储原数组和差分数组
-
差分数组的核心思想:
- 区间操作降维:差分数组将"区间 \([l, r]\) 加 \(c\)"这一需要修改 \(r-l+1\) 个元素的操作,转化为两个单点更新 \(b[l] += c\) 和 \(b[r+1] -= c\),实现 \(O(1)\) 的区间标记
- 前缀和还原:差分是前缀和的逆运算。对差分数组求前缀和,恰好将 \(b[l]\) 处的 \(+c\) 传播到 \(l\) 及之后的所有位置,而 \(b[r+1]\) 处的 \(-c\) 从 \(r+1\) 开始抵消,最终效果仅为 \([l, r]\) 区间增加 \(c\)
- 离线处理优势:先记录所有操作的差分标记,最后统一通过前缀和还原,避免了重复遍历数组,特别适合大量区间更新后统一查询的场景
- 边界处理技巧:在 \(r+1\) 处减 \(c\) 而非 \(r\) 处,确保只有 \([l, r]\) 内的位置受到影响,\(r = n\) 时 \(r+1 = n+1\) 需数组多开一位
- 适用于区间加减、区间更新、离线查询类问题,与前缀和互为逆运算
【解题思路】

【算法标签】
差分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 100010; // 定义数组最大长度
int n, m; // n: 数组长度, m: 操作次数
int a[N]; // 原始数组
int b[N]; // 差分数组
/**
* 差分数组区间更新操作
* @param l 区间左端点
* @param r 区间右端点
* @param c 要增加的值
*/
void insert(int l, int r, int c)
{
b[l] += c; // 左端点加c
b[r + 1] -= c; // 右端点+1位置减c
}
int main()
{
// 输入数组长度和操作次数
scanf("%d%d", &n, &m);
// 输入原始数组
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}
// 初始化差分数组(两种方式等价)
// 方式1: 直接计算差分 b[i] = a[i] - a[i-1]
// 方式2: 通过insert函数逐个元素插入
for (int i = 1; i <= n; i++)
{
insert(i, i, a[i]);
}
// 处理每个区间更新操作
while (m--)
{
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
insert(l, r, c); // 执行区间更新
}
// 计算前缀和,得到更新后的数组
for (int i = 1; i <= n; i++)
{
b[i] += b[i - 1];
}
// 输出最终数组
for (int i = 1; i <= n; i++)
{
printf("%d ", b[i]);
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 100005; // 定义最大数组长度
int n; // 数组元素个数
int m; // 操作次数
int s[N]; // 原始数组/前缀和数组
int a[N]; // 差分数组
int main()
{
// 输入数组长度和操作次数
cin >> n >> m;
// 输入原始数组(作为前缀和数组)
for (int i = 1; i <= n; i++)
{
cin >> s[i];
}
// 将前缀和数组转换为差分数组
// 差分数组定义:a[i] = s[i] - s[i-1]
for (int i = 1; i <= n; i++)
{
a[i] = s[i] - s[i - 1];
}
// 处理每个区间加减操作
while (m--)
{
int L, R, c; // 操作参数:区间[L,R]加上值c
cin >> L >> R >> c;
// 差分数组的核心操作:
// 在L位置加c,会影响从L开始的所有前缀和
a[L] += c;
// 在R+1位置减c,抵消从R+1开始的影响
// 这样只有区间[L,R]内的元素会受到影响
a[R + 1] -= c;
}
// 通过差分数组重新计算前缀和数组
// 恢复更新后的数组值
for (int i = 1; i <= n; i++)
{
s[i] = s[i - 1] + a[i];
}
// 输出更新后的数组
for (int i = 1; i <= n; i++)
{
cout << s[i] << " ";
}
return 0;
}
【运行结果】
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
3 4 5 3 4 2
浙公网安备 33010602011771号