把多次区间加分转成差分数组的两个端点修改,最后前缀还原并维护最低成绩。
OJ: luogu
题目 ID: P2367
难度:普及-
标签:差分前缀和模拟python
创建: 2026-06-18 19:16
更新: 2026-10-07 12:15
后置题目
luogu P3368【模板】树状数组 2B 的区间加单点查把 A 教的差分两端点修改逐字搬进树状数组(d[l]+=x、d[r+1]-=x,位置值是差分前缀和),再叠加 Fenwick 维护与越界 r+1 判断luogu P2882[USACO07MAR] Face The Right Way GB 把 A 的区间修改端点差分标记思想用在区间翻转上:起点打 started[i]=1,扫描到 i-K 时异或消去,使每次翻转 O(1)luogu P3406海底高铁B 复用 A 的区间加差分(左端加、右端+1 减)再前缀和还原这一步,逐段统计使用次数后贪心取 min(count*A, count*B+C)usaco 1373Balancing BacteriaB 的满分做法先复用 A 的「相邻差分数组」这一步定义一阶差分 b_i=a_i-a_{i-1},再差分出二阶差分把后缀阶梯操作压成单点修改并贪心从左修到右luogu P5542[USACO19FEB] Painting The Barn SB 把 A 的区间加改差分端点步骤推广到二维矩形四点差分,最后同样用前缀和统一还原luogu P2879[USACO07JAN] Tallest Cow SB 把 A 教的「区间整体加减塌缩成差分两端修改、最后前缀和还原」整步照搬(区间减 1 即 D[L]-=1、D[R+1]+=1),再叠加 A 未教的视线关系转化成区间压制与重叠累加、关系去重。HDU 5124linesB 原样复用 A 的区间加改 L 与 r+1 差分端点步骤,再叠加离散化处理 10^9 的大坐标luogu P3948数据结构B 的 A L R X 区间加直接复用 A 的「区间加只动 diff[l] 与 diff[r+1] 两个边界点再前缀和还原」这一步,再叠加 A 未教的少量 Q 查询暴力还原与答案前缀和 O(1) 回答 Final。
题意
有 n 个学生,每个学生有一个初始语文成绩。
接下来有 p 次修改,每次给出 l r z,表示把第 l 到第 r 个学生的成绩都增加 z。
要求输出所有修改完成后,全班最低的语文成绩。
思路
先看一个可以直接验证想法的朴素解:
cpp
#include <bits/stdc++.h>
using namespace std;
int a[10005];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, p;
cin >> n >> p;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= p; i++) {
int l, r, z;
cin >> l >> r >> z;
// 朴素做法:把区间里的每个学生成绩都加上 z。
for (int j = l; j <= r; j++) {
a[j] += z;
}
}
int ans = a[1];
for (int i = 2; i <= n; i++) {
if (a[i] < ans) ans = a[i];
}
cout << ans << '\n';
return 0;
}朴素做法每次修改都枚举 [l,r] 中的所有学生。它很容易理解,但 n 和 p 都可能达到 5 * 10^6,最坏情况下无法通过。
这题的操作都是“连续区间整体加一个数”,并且中间没有查询,最后才求结果。按照 rbook《差分》文章中的模型,这正是普通差分适用的场景。
设差分数组为:
text
diff[i] = a[i] - a[i-1]如果要让 [l,r] 每个数都加 z,只需要:
text
diff[l] += z
diff[r+1] -= z因为前缀和还原时,从 l 开始都会多累加到 z,从 r+1 开始这个 z 又被抵消。
一次修改的影响
这张表展示一次 [l,r] 加 z 对不同位置的影响。
| 位置范围 | 前缀和中是否包含 +z |
前缀和中是否包含 -z |
最终变化 |
|---|---|---|---|
i < l |
否 | 否 | 不变 |
l <= i <= r |
是 | 否 | 增加 z |
i > r |
是 | 是 | 抵消,不变 |
读入初始成绩时,也可以把第 i 个成绩看成一次 [i,i] 的区间加法,直接写进同一个差分数组。
最后从左到右做前缀和还原每个学生的最终成绩,同时维护最小值即可。
Python 知识
array("i")用连续的 32 位整数保存五百万项差分,避免 Pythonlist[int]的对象开销。os.read分块解析整数,不会让超长输入行的split()瞬间创建数百万个字节串。islice(difference, n)只遍历有效部分,不复制大数组。
代码
python
import os
from array import array
from itertools import islice
def read_ints():
number = 0
sign = 1
reading = False
while chunk := os.read(0, 1 << 20):
for byte in chunk:
if 48 <= byte <= 57:
number = number * 10 + byte - 48
reading = True
else:
if reading:
yield sign * number
number = 0
sign = 1
reading = False
elif byte == 45:
sign = -1
if reading:
yield sign * number
data = iter(read_ints())
n, operations = next(data), next(data)
difference = array("i", [0]) * (n + 1)
previous = 0
for i in range(n):
score = next(data)
difference[i] = score - previous
previous = score
for _ in range(operations):
left, right, change = next(data), next(data), next(data)
difference[left - 1] += change
difference[right] -= change
score = 0
answer = 1 << 60
for change in islice(difference, n):
score += change
answer = min(answer, score)
print(answer)复杂度
- 建立差分数组需要
。 - 每次修改只改两个端点,
p次修改需要。 - 最后还原并求最小值需要
。 - 总时间复杂度
。 - 空间复杂度
。
总结
这题是“一维差分”的直接应用。
看到大量区间加法,并且所有操作结束后才查询最终结果,就应该先想到差分。差分的作用是把一次区间修改压缩成两个端点修改,最后再用一次前缀和把所有影响还原出来。