椰程信奥·互动课件 专题5·前缀和与差分(一)
10:00 1 / 15
🧭 椰程信奥 · CSP复赛专题系列

专题5 · 前缀和与差分

前缀和与差分是一对互逆的数组工具:
前缀和把「区间求和」压成 O(1),差分把「区间加减」压成 O(1)。
本讲拆开它们的定义、互逆关系,并用两道经典真题练手。

USACO18DEC 水桶清单 增减序列 区间求和 / 区间修改
动机

从 O(n²) 到 O(1):一个问题逼出的优化

有一列数,要反复问「第 l 个到第 r 个加起来是多少」——最直接的写法会超时。

🐢 暴力:每次从头加

int sum=0;
for(int i=l;i<=r;i++) sum+=a[i];

单次 O(n)。问 m 次就是 O(n·m),n=m=10⁵ 时就是 10¹⁰,必炸。

🚀 前缀和:预处理一次

s[i]=s[i-1]+a[i];      // 预处理 O(n)
ans = s[r]-s[l-1];      // 每次 O(1)

总复杂度 O(n+m)。多花的只是 s 这一个数组的空间。

关键认识:前缀和不是新算法,而是一种「用空间换时间」的预处理思想——把重复计算的结果先存起来。凡是「同一份数据被反复查询」,都要先想到它。
看到「多次询问某个区间」且「数组不会中途被修改」,95% 是前缀和。如果数组会被修改,那是线段树/树状数组的活,别硬套。
3D🎲 前缀和 = 累积塔,区间和 = 切一段🖱 拖拽旋转 · 双击复位
为什么用 3D:s[i] 是一座能旋转着看的累积塔。s[r] − s[l−1] 表现为「下半截(红)减掉、上半截(绿)留下」,比二维条形图更能讲清「多退少补」。
定义

前缀和:s[i] 是前 i 项的和

📐 两条等价写法

递推式:s[i] = s[i-1] + a[i],s[0]=0。
定义式:s[i] = a[1]+a[2]+…+a[i]。

区间和:sum(l,r) = s[r] - s[l-1]。

🧠 为什么减的是 l-1

s[r] 含 a[1]…a[r];s[l-1] 含 a[1]…a[l-1]。
相减后正好剩下 a[l]…a[r]。

这一步是最经典的「多退少补」:多算的部分减掉,而不是重新数一遍。

🖼️ 看图:为什么是 s[r] − s[l−1](“多退少补”)

例子:a = [2, 4, 6, 1, 3, 5],要求第 4 到第 6 项的和(答案是 1+3+5 = 9) 原数组 2 4 6 1 3 5 a[1] a[2] a[3] a[4] a[5] a[6] s[6] = 前 6 项和 s[6] = 2+4+6+1+3+5 = 21 s[3] = 前 3 项和 s[3] = 12 相减之后 21 − 12 = 9 ✓ 📌 多一些的(前 3 项)减掉,剩下的正好是第 4~6 项 —— 这就是“多退少补”。
实操要点:数组一律开成 1-based(a[1] 是第一个数),并让 s[0]=0。这样 l=1 时 s[l-1]=s[0] 天然合法,不用为 l=1 写特判——这是 0-based 写法最容易越界的地方。
「减一不减己,从零开始起」:求 [l,r] 就减 s[l-1];数组从 1 开始、s[0] 恒为 0。背下这句,前缀和就不会再写出 s[l] 这种经典笔误。
定义

差分:b[i] 记录「比前一个多多少」

📐 定义式

b[i] = a[i] - a[i-1](规定 a[0]=0,故 b[1]=a[1])。

差分数组求前缀和 = 原数组:
b[1]+…+b[i] = a[i]。

🔁 核心操作(背下来)

要把 a 的 [l, r] 全部加 x:

b[l]   += x;
b[r+1] -= x;

之后对 b 求前缀和,得到的就是修改后的 a。

为什么这样对?b[l]+=x 让前缀和从 l 起永久抬升 x;b[r+1]-=x 再从 r+1 起把它抵消回去。中间正好是 [l,r] 这一段被抬高——这就是「区间修改 O(1)」的全部秘密。
要用到 b[r+1],而 r 最大为 n,所以数组至少开 n+2(再宽一格更保险)。这是差分题 最高频的越界 WA。
对照

前缀和 与 差分 的互逆关系

原数组 ↔ 前缀和 ↔ 差分:一句话记住方向原数组 a13254前缀和 s(s[i] = a[1..i] 的和)1461115区间和 a[l..r] = s[r] − s[l−1]查一次:从 O(n) 变成 O(1)差分 d(d[1]=a[1],d[i]=a[i]−a[i−1])12−13−1区间加 x:d[l] += x,d[r+1] −= x(右端点 +1 是最经典的坑)

🔄 三句话记住

①差分数组求前缀和 = 原数组。
②前缀和数组求差分 = 原数组。
③所以:前缀和负责区间查询,差分负责区间修改。

前缀和擅长

  • 多次问「[l,r] 的和/最大值/异或和」
  • 数组不修改、查询很频繁
  • 例:水桶清单最后那一步「取峰值」

差分擅长

  • 多次「[l,r] 全部加/减 x」
  • 只在最后统一查询一次
  • 例:水桶清单的加桶、增减序列的操作
组合套路:「差分做修改 → 前缀和还原 → 再取答案」是 CSP 最常考的一条流水线,两道例题都走这条线。
互动

前缀和 · 区间查询模拟器

样例
点击「下一步」,先一步步把 s[i] 算出来。
互动

差分 · 区间加模拟器

样例
点击「下一步」,看 b[l]+=x / b[r+1]-=x 如何改写差分数组。
例题 1 / USACO18DEC

水桶清单:区间加 + 取峰值

N 头牛,第 i 头在 [s_i, t_i] 内占用 b_i 个桶。问同一时刻最多有多少桶被占用。

🔍 建模

把「时刻」当作下标(1…1000)。每头牛的操作就是:给区间 [s_i, t_i] 加上 b_i。

全部加完后,问「每个时刻用了多少桶」的最大值。

⚡ 两种写法对比

暴力:每头牛循环 t-s+1 次,总计 O(N×1000)。N≤100 其实能过,但思路不通用。

差分:每头牛只改 2 个位置 → O(N);最后扫一遍求前缀和取最大 → O(1000)。

易错点:是 d[t+1] -= b,不是 d[t]。区间是闭区间 [s,t],t 这一刻仍在占用,要减到 t 的下一个时刻去。写成 d[t] 会让 t 时刻少算一头牛,样例都对但大数据必错。
例题 1

水桶清单 · 代码走读

#include <iostream>
#include <algorithm>
using namespace std;
int d[1105];                       // 时刻最大 1000,多开一点
int main(){
  int n; cin>>n;
  for(int i=0;i<n;i++){
    int s,t,b; cin>>s>>t>>b;
    d[s]   += b;                     // 从 s 起抬升
    d[t+1] -= b;                     // 到 t+1 抵消(不是 t!)
  }
  int cur=0, ans=0;
  for(int t=1;t<=1000;t++){
    cur += d[t];                     // 前缀和还原本时刻用桶数
    if(cur>ans) ans=cur;           // 顺手取最大
  }
  cout << ans << "\n";
  return 0;
}
三个坑:① 数组开到 1105 而不是 1005(因为要用 t+1,t 可达 1000);② 扫描上界取 1000,别写成 n;③ ans 初值设 0 即可(桶数非负),若题目可能出现负数则初值要设成极小值。
例题 2 / 增减序列

增减序列:把「操作」翻译成差分

每次给 [l,r] 全加 1 或全减 1,求让全数列相等的最少操作数与结果种数。

🔑 第一步:翻译操作

「[l,r] +1」在差分上就是 d[l]+=1; d[r+1]-=1;。
「全部相等」⇔ d[2..n] 全为 0。

🎯 第二步:配对消元

设 P = d₂…dₙ 中正数之和,Q = 负数绝对值之和。

  • 一个 +1 和一个 -1 可配对用一次操作同时消掉。
  • 配完剩下的只能单独消。
结论(背):最少操作数 = max(P, Q);结果种数 = |P−Q| + 1。
样例 a=[1,1,2,2]:d₂=0, d₃=1, d₄=0 → P=1, Q=0 → 操作数 1、种数 2。✓
配对用掉 min(P,Q) 次后,剩下 |P−Q| 次「单消」操作,每一次可以选择作用于前缀 [1,k] 还是后缀 [k+1,n],也可以理解为最终可以把整个数列抬到 |P−Q|+1 种不同高度。多出来的这一份自由度,就是种数 = |P−Q|+1。
互动

增减序列 · 配对消元模拟器

样例
点击「下一步」,看正数与负数如何两两配对消掉。
诊断

高频易错辨析

① 求区间 [l,r] 的和,前缀和怎么写?

② 把 a 的 [l,r] 全部加 x,差分数组要改哪两处?

③ 增减序列中 P=5、Q=3,最少操作数与结果种数各是多少?

拿分

一道题 100 分是分档给的——不会正解也要先拿保底

复赛一道题的 100 分由十几到二十几个测试点组成, 前面几档专门为「没想出正解的人」准备。四道题各拿 30~40 分, 加起来往往就是一等奖和二等奖的分界。

第 1 档 特判 5~10 分 约 2 分钟 第 2 档 暴力 20~30 分 约 8 分钟 第 3 档 特殊性质 40~60 分 约 15 分钟 第 4 档 正解 100 分 约 25 分钟

① 特判档:先别想算法,看数据范围表

题目给的数据范围表就是出题人给你的送分清单。看到「n = 1」「只有一组数据」这类一行, 就先写个 if 直接输出答案。2 分钟换 5~10 分,全场最划算。

② 暴力档:按题意最直白地写一遍

多重循环、DFS 全枚举、朴素 O(n³)……不要优化,只要保证最小那档全对。 它既是保底分,又是后面对拍的标尺——没有它你无法证明正解是对的。

③ 特殊性质档:题目里那句「若……」

常见的送分性质:数据已经有序、所有值完全相同、规模小到可以 O(n²)、 只出现一种类型。题目写出来就是让你拿的,专门写一份即可。

④ 正解档:思路定了,就赢了八成

考场上的时间几乎都花在想思路上,不是写代码。写完先跑样例, 再用第 ② 档的暴力版造小数据对拍——这是唯一能证明你思路对的办法。

考场纪律:每档设时间上限,到点还没调通就 立刻提交当前版本,保住已有分数,再往上冲。最常见的翻车是"正解写了 50 分钟没过,连暴力分都没交上去"。
拿分

本讲 2 道题,各自的四档怎么走

水桶清单 · BUCKET

第1档 · 2 分钟
n == 1 → 直接输出它的 b

第2档 · 8 分钟
开数组,每个区间逐格 +b,最后扫最大值;时间 ≤ 1000 稳过

第3档 · 10 分钟
所有 b == 1 → 退化成「区间覆盖计数」

第4档 · 15 分钟
差分 d[s]+=b, d[t+1]−=b,前缀和还原后取 max

增减序列 · INCDEC

第1档 · 2 分钟
n == 1 → 0 次、1 种结果

第2档 · 8 分钟
逐次选区间模拟(左右配对消),n ≤ 1000 稳过

第3档 · 10 分钟
只做第一问(最少次数),先不管方案数

第4档 · 15 分钟
差分数组正数之和 P、负数绝对值之和 Q;次数 = max(P,Q),方案数 = |P−Q|+1

50 分钟时间盒(一题的节奏):
0–5 分钟 读题 + 圈出数据范围表里最小的那几档 → 5–15 分钟 写完第 1、2 档并先交一次(保住 20~30 分)→ 15–40 分钟 冲第 3、4 档 → 40–50 分钟 造小数据对拍、检查 freopen 与文件名。
课堂上别让学生"从头想正解"。先要求每个人 15 分钟内交出第 1、2 档, 再放开冲正解。这个顺序一换,平均分通常能涨一大截——因为它杜绝了"想了 40 分钟,交上去 0 分"。
小结

本讲知识树与自检清单

🌳 知识树

前缀和:定义 s[i]=s[i-1]+a[i] → 区间和 s[r]-s[l-1] → 1-based + s[0]=0。

差分:定义 b[i]=a[i]-a[i-1] → 区间加 b[l]+=x, b[r+1]-=x → 前缀和还原。

流水线:差分改 → 前缀和还原 → 取答案。

✅ 考场 N 步自检

  • 下标从 1 开始了吗?s[0]/b[0] 是不是 0?
  • 减的是 l-1 吗?抵消的是 r+1 吗?
  • 数组开够 n+2 了吗?(r+1 会越界)
  • 和最大可能到多少?n=10⁵、a_i=10⁹ 时要 long long。
  • 最后一步是不是把差分还原成原数组再取答案?
下机前:把两道题的标程各默写一遍——水桶清单(d[s]+=b, d[t+1]-=b)、增减序列(P/Q 与 max/|P−Q|+1)。能默写 ≈ 本讲过关。

目录