参考 https://class.luogu.com.cn/course/yugu25ajc 第一天内容

登录以参加训练计划

“基础算法串讲:桶、前缀和与差分、二分、贪心”,是 CSP-J(普及组)最核心、最必考的四大基石。如果说数据结构是内功,那这四个就是你行走江湖最常用的四套剑法。

下面我为你详细拆解每一个知识点的核心逻辑、应用场景以及考场上的“坑”。


一、 桶 (Bucket) / 计数排序

1. 核心思想 “空间换时间”。 不把数字 xx 当作数值,而是把它当作数组的下标。 比如有一个数组 cntcnt[x] 表示数字 xx 出现了多少次,或者 xx 是否存在。

2. 适用场景

  • 数值范围小:数据的数值(Value)不大(比如 1x1061 \le x \le 10^6),哪怕数据量(NN)很大也没关系。
  • 去重/排序:需要快速排序且数值密集,或者需要快速判断“有没有”。
  • 找众数:统计谁出现次数最多。

3. 代码模板

int bucket[1000005]; // 注意内存限制,通常不超过 10^7
// 统计
for(int i=1; i<=n; i++) {
    cin >> x;
    bucket[x]++;
}
// 遍历(天然有序)
for(int i=0; i<=max_val; i++) {
    while(bucket[i] > 0) {
        cout << i << " ";
        bucket[i]--;
    }
}

4. 教练提示

  • 坑点:如果题目说数字可能达到 10910^9,或者有负数,普通的数组桶就失效了。这时需要用 std::map 或者先进行“离散化”。

二、 前缀和与差分 (Prefix Sum & Difference)

这两个是一对互逆运算,专门用来解决区间问题,能把 O(N)O(N) 的操作降维到 O(1)O(1)

1. 前缀和 (Prefix Sum)

  • 定义S[i]=a[1]+a[2]++a[i]S[i] = a[1] + a[2] + \dots + a[i]
  • 递推公式S[i] = S[i-1] + a[i]
  • 必杀技O(1)O(1) 求静态区间和
    • a[L]a[L]a[R]a[R] 的和?
    • 答案直接是:S[R] - S[L-1]
  • 进阶:二维前缀和(容斥原理)。

2. 差分 (Difference)

  • 定义D[i]=a[i]a[i1]D[i] = a[i] - a[i-1]
  • 性质:差分数组的前缀和 = 原数组。
  • 必杀技O(1)O(1) 进行区间修改
    • 任务:把区间 [L,R][L, R] 内所有数都加上 vv
    • 暴力做法要 O(N)O(N),差分做法只要两步:
      1. D[L] += v
      2. D[R+1] -= v (注意是 R+1)
  • 流程:构建差分 \to 修改差分 \to 求前缀和还原数组。

3. 教练提示

  • 下标:强烈建议数组下标从 1 开始,这样 S[L-1]L=1L=1 时就是 S[0]=0,不用特判边界。

三、 二分 (Binary Search)

O(N)O(N) 的查找优化为 O(logN)O(\log N)。前提是答案具有单调性(有序)。

1. 二分查找 (Binary Search on Array)

在一个有序数组里找某个数,或者找第一个 x\ge x 的数。

  • STL 神器lower_bound (找 \ge) 和 upper_bound (找 >>)。
  • 手写mid = (l + r) >> 1

2. 二分答案 (Binary Search on Answer) —— 重点中的重点

题目让你求“最大值最小”或“最小值最大”时,90% 是二分答案。

  • 思路
    1. 不去直接算答案,而是一个答案 midmid
    2. 写一个 check(mid) 函数:如果答案是 midmid,能满足题目要求吗?
    3. 如果满足,就尝试更好的答案;如果不满足,就换个方向猜。
  • 模板(查找左边界):
    int l = 1, r = 1e9, ans = -1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (check(mid)) {
            ans = mid; // 记录答案
            r = mid - 1; // 往左找更小的
        } else {
            l = mid + 1;
        }
    }
    

3. 教练提示

  • 死循环:手写二分最容易写成死循环(l=mid 还是 mid+1?)。建议固定背诵一种模板(如 l <= r 配合 mid+1/mid-1),或者直接用记录 ans 的方法。

四、 贪心 (Greedy)

1. 核心思想 “目光短浅”。在每一步选择中,都采取在当前状态下最好/最优的选择(局部最优),从而希望导致结果是全局最优。

2. 常见套路

  • 区间问题:比如“最多能参加多少个活动”。通常按右端点排序。
  • 排序不等式:比如“排队打水问题”,让快的人先打,总等待时间最短。
  • 合并果子:每次挑最小的两堆合并(配合优先队列)。
  • 字典序:为了让字符串字典序最小,通常把小的字符尽可能放在前面。

3. 教练提示

  • 玄学:贪心是最容易想的,也是最容易错的。
  • 验证:在考场上,如果你想到了一个贪心策略,最好先手动模拟几个特殊的样例(比如极端的、相等的、包含的),看看对不对。
  • 结合:贪心经常和二分结合(在 check 函数里贪心)。

总结

这四个知识点在 CSP-J/S 里的地位:

  1. :基础工具,用来优化统计。
  2. 前缀和/差分:必考的预处理技巧,看到“区间查询”或“区间修改”就要由条件反射。
  3. 二分:解决“最大值最小”类问题的通解。
  4. 贪心:思维题的核心,决定你能否快速切掉前两题。

把这四块弄通透,普及组一等奖就稳了一大半!加油!


你好!我是你的 OI 教练。

这是一个非常好的实战请求。理解了理论(桶、前缀和等)之后,必须通过刷题来固化肌肉记忆。

“桶”、“Map”和“离散化” 其实是处理数据映射的三种不同境界,对应了不同的数据规模。

我为你精选了洛谷上具有代表性的题目,按难度和考点分类整理如下:


一、 数组桶 (Bucket) / 计数排序

特点:数值小(通常 107\le 10^7),速度最快 O(1)O(1),但吃内存。

题目编号 题目名称 难度 训练重点
P1271 [深基9.例1] 选举学生会 🔴 入门 裸题。数值极小,直接开桶统计并输出,最基础的计数排序。
P1047 [NOIP2005 普及] 校门外的树 🟠 普及- 必刷。用布尔数组桶 vis[x] 标记某个位置是否有树,经典的覆盖问题。
P1059 [NOIP2006 普及] 明明的随机数 去重+排序。利用桶的天然有序性和唯一性,一次遍历解决去重和排序。
P5744 [深基7.习9] 培训 🔴 入门 虽然是结构体,但结合简单的桶思想可以统计分数段等信息。
P1428 小鱼比可爱 虽然是暴力题,但可以用桶思想理解(统计比自己小的)。

二、 STL std::map (映射)

特点:数值大(甚至可以是字符串),比较稀疏,速度稍慢 O(logN)O(\log N),写法最简单。

题目编号 题目名称 难度 训练重点
P1102 A-B 数对 🟡 普及/提高- 经典。暴力 O(N2)O(N^2) 会超时,用 map 统计每个数出现的次数,将查找变为 O(logN)O(\log N)O(1)O(1)
P5266 [深基17.例6] 学籍管理 模板题。纯粹考察 map 的增删改查操作,Key 是字符串(姓名)。
P3879 [TJOI2010] 阅读理解 一对多映射。单词对应多篇文章,考察 map<string, vector<int>> 的用法。
P1918 保龄球 🟠 普及- 给定数值找下标。由于数值很大,无法开数组,必须用 map 记录 mp[val] = index
P3370 【模板】字符串哈希 🟡 普及/提高- 虽然本意是考 Hash 算法,但用 std::mapstd::set 也能通过(卡常除外),适合练习去重。

三、 离散化 (Discretization)

特点:数值极大(10910^9)但数据个数少(N105N \le 10^5),只关心相对大小,不关心绝对数值。通常配合线段树、树状数组或并查集使用。

核心代码模板(必背):

sort(a + 1, a + n + 1);
int len = unique(a + 1, a + n + 1) - (a + 1);
// 查询 x 离散化后的值:
int id = lower_bound(a + 1, a + len + 1, x) - a;
题目编号 题目名称 难度 训练重点
P1496 火烧赤壁 🟡 普及/提高- 基础。坐标达到 2312^{31},但只有几千个区间。需要把坐标离散化后再计算覆盖长度。
P1955 [NOI2015] 程序自动分析 🔵 提高+/省选- 经典综合。并查集题目,但数字高达 10910^9,必须先离散化才能放入并查集的数组中。
P1908 逆序对 🟡 普及/提高- 求逆序对。如果用树状数组做,数值很大时必须离散化(当然归并排序做法不需要)。
P3029 [USACO11NOV] Cow Lineup S 双指针+离散化。奶牛的 ID 是大整数,需要映射成小的 ID 才能开数组统计频率。
P8218 [深基10.例6] 求区间和 🟠 普及- 虽然这题本身不需要,但你可以试着假设 N=105N=10^5 但数值很大,强行用离散化+前缀和来练习。

🏆 教练的总结建议

这三种技术其实是解决同一个问题(如何通过数值找位置)的三个阶段:

  1. 能开数组桶,就开数组桶

    • 题目说 ai106a_i \le 10^6 \rightarrow 既然能用 int a[1000005],就别搞复杂的。
    • 例题:P1271
  2. 数值很大,或者是字符串,且不需要维护复杂的区间关系

    • map。虽然慢一点(带 log),但代码只需要两行,考场上性价比最高。
    • 例题:P1102, P5266
  3. 数值很大,且需要作为数组下标去构建高级数据结构(如树状数组、并查集)

    • 必须用 离散化map 的 log 可能会导致总时间超时(TLE),或者 map 无法支持树状数组的连续性要求。
    • 例题:P1955, P1908

建议按照 数组桶 \to Map \to 离散化 的顺序刷题,循序渐进!加油!


前缀与差分洛谷题单:

前缀和 (Prefix Sum)差分 (Difference) 是 OI 中性价比最高的算法之一。它们的特点是:代码短,数学味重,能把复杂度从 O(N)O(N) 降到 O(1)O(1)

这对算法就像“倚天剑”和“屠龙刀”:

  • 前缀和:专攻 “静态区间查询”
  • 差分:专攻 “区间修改”

下面我为你整理了一份循序渐进的洛谷刷题单,从板子题到经典应用题,按难度梯队排列。


第一阶段:一维基础(板子题)

目标:背诵并熟练掌握一维的公式,坚决不写 O(N2)O(N^2) 的暴力。

  • 前缀和公式sum[i] = sum[i-1] + a[i];区间 [L, R] 和为 sum[R] - sum[L-1]
  • 差分公式b[L] += v; b[R+1] -= v;最后做一次前缀和还原。
题目编号 题目名称 难度 训练重点
P8218 [深基10.例6] 求区间和 🟠 普及- 【纯板子】 这就是前缀和定义的直接应用。一定要注意下标从 1 开始,防止越界。
P2367 语文成绩 【纯板子】 这就是差分定义的直接应用。全班修改成绩,最后一次性输出。
P1115 最大子段和 经典贪心/DP。但也可以用前缀和理解:sum[i] - min(sum[0...i-1]) 就是以 i 结尾的最大子段和。
P5638 【CSGRound2】光骓者的荣耀 应用。求某段区间的和(前缀和)+ 简单的贪心选择。

第二阶段:二维进阶(矩阵操作)

目标:掌握二维空间下的公式,这是一个难点,容易写错坐标。

  • 二维前缀和(容斥原理):S[i][j] = S[i-1][j] + S[i][j-1] - S[i-1][j-1] + a[i][j]
  • 子矩阵和S[x2][y2] - S[x1-1][y2] - S[x2][y1-1] + S[x1-1][y1-1]
  • 二维差分D[x1][y1]+=c; D[x1][y2+1]-=c; D[x2+1][y1]-=c; D[x2+1][y2+1]+=c
题目编号 题目名称 难度 训练重点
P1719 最大加权矩形 🟡 普及/提高- 二维前缀和。需要枚举矩阵的左上角和右下角(或者压缩行),利用二维前缀和 O(1)O(1) 算矩阵内的值。
P2004 领地选择 必做。在一个大地图里找一个 C×CC \times C 的正方形,使其和最大。标准的二维前缀和应用。
P3397 地毯 🟠 普及- 二维差分。铺地毯问题。这题既可以用“一维差分(对每一行做)”,也可以用“二维差分”直接做。

第三阶段:巧妙应用(数学与思维)

目标:题目没有明说“求区间和”,但你需要把问题转化为前缀和/差分模型。

题目编号 题目名称 难度 训练重点
P3406 海底高铁 🟡 普及/提高- 差分应用。看起来是图论或模拟,其实是统计每段铁路被经过了多少次。用差分数组标记覆盖次数。
P3131 [USACO16JAN] Subsequences... 同余前缀和。求区间和能被 7 整除。利用性质:若 (S[i] - S[j]) % 7 == 0,则 S[i] % 7 == S[j] % 7。结合 桶/Map 来记录余数位置。
P1314 [NOIP2011 提高] 聪明的质监员 🔵 提高+/省选- 二分答案+前缀和。非常经典的综合题。需要二分 WW,check 过程中用前缀和快速计算检验值。

第四阶段:压轴神题(差分+二分)

目标:掌握 CSP-S / NOIP 级别的常见套路,即“二分答案,差分验证”。

题目编号 题目名称 难度 训练重点
P1083 [NOIP2012 提高] 借教室 🟡 普及/提高- 必刷神题! 问第几个订单不满足。可以二分订单数量,用差分数组快速模拟前 midmid 个订单的修改,判断是否爆仓。
P4552 [Poetize6] IncDec Sequence 🔵 提高+/省选- 差分性质。把一个序列变成全相等最少几步。这题考察的是对差分数组本质的理解(让 b[2]b[n]b[2] \dots b[n] 全变为 0)。

📝 教练的刷题建议

  1. 先刷板子:P8218 和 P2367 必须能够闭着眼睛写出来,特别是 sum[R] - sum[L-1] 这里的 -1,以及差分还原时的 R+1
  2. 注重下标:做这类题,强烈建议数组下标从 1 开始。这样处理边界(比如 L1L-1 变成 0)时,sum[0] 天然是 0,不需要写 if (L==0) 这种特判,能省去很多 Bug。
  3. 遇到区间修改先想差分:只要题目说“把一段区间加上一个数”,或者“把一段路涂上颜色”,第一时间反应就是差分。
  4. 遇到区间求和先想前缀和:只要题目需要反复询问不同区间的和,前缀和就是 O(N)O(N) 预处理,O(1)O(1) 询问的神器。

你好!我是你的 OI 教练。

二分 (Binary Search) 是算法竞赛中最核心、最通用、最容易在考场上拿分的算法之一。

它的本质不是“折半查找”,而是**“根据单调性缩小答案范围”。只要你发现题目答案具有“多一分则太大,少一分则太小”**的性质,就能用二分。

我为你整理了一份循序渐进的刷题单,分为四个阶段,从基础查找一直到 NOIP 提高组的经典难题。


第一阶段:二分查找基础(数组与 STL)

目标:不涉及复杂的 check 函数,仅仅是在一个有序数组里找数。 重点:学会手写二分模板,以及熟练使用 STL 的 lower_boundupper_bound

题目编号 题目名称 难度 训练重点
P2249 【深基11.例1】查找 🟠 普及- 【纯板子】 给定有序数组,找第一个等于 qq 的位置。练习 lower_bound 的最佳题目。
P1102 A-B 数对 🟡 普及/提高- 转化AB=CAC=BA-B=C \Rightarrow A-C=B。枚举 AA,二分查找 BB 出现的次数(upper - lower)。
P1678 烦恼的高考志愿 🟠 普及- 查找最近值。在有序数组里找绝对值差最小的数。需要比较 lower_bound 指向的位置和它前一个位置。
P8647 [蓝桥杯 2017 省 AB] 分巧克力 🟡 普及/提高- 简单二分答案。切出 KK 块大小为 xx 的正方形,求 xx 最大是多少。

第二阶段:二分答案(整数域)—— 核心必考

目标:题目没有直接给你有序数组,而是让你求一个**“最大值最小”或者“最小值最大”**的答案。 套路:二分答案 mid \to 编写 check(mid) 函数验证可行性 \to 调整区间。

题目编号 题目名称 难度 训练重点
P1873 [COCI 2011/2012] Eko / 砍树 🟠 普及- 入门经典。伐木机高度 HH 越高,得到的木材越少(单调性)。求满足要求的最高 HH
P2440 木材加工 同类题。切段问题,和切巧克力类似,二分段的长度。
P2678 [NOIP2015 提高] 跳石头 🟡 普及/提高- 【背诵级神题】 “最短跳跃距离的最大值”。经典的贪心 check:如果设最短距离为 midmid,需要移走多少块石头?
P1182 数列分段 Section II 【经典】 “每段和的最大值最小”。check:如果每段和不超过 midmid,最少能分成几段?
P3853 [TJOI2007] 路标设置 应用。公路上增设路标,使得最大空旷距离最小。check 逻辑简单清晰。

第三阶段:二分答案(实数域)

目标:处理小数/浮点数。 重点:循环条件通常是 while (r - l > 1e-5) 或者直接 for(int i=0; i<100; i++)(二分 100 次精度绝对够了)。

题目编号 题目名称 难度 训练重点
P1163 银行贷款 🟠 普及- 金融计算。利率越高,还款压力越大。二分利率,计算分期还款总额。
P1024 [NOIP2001 提高] 一元三次方程 🟡 普及/提高- 数学。在区间内找零点。题目保证了根的范围,分段进行二分查找。
P3743 小鸟的设备 应用。二分使用时长。所有设备用电速度之和与充电宝速度的关系。

第四阶段:二分 + 其他算法(综合应用)

目标:二分只是外壳,check 函数内部可能涉及前缀和、差分、图论甚至 DP。这是提高组/省选的常见模式。

题目编号 题目名称 难度 训练重点
P1083 [NOIP2012 提高] 借教室 🟡 普及/提高- 二分+差分。二分订单数量,check 函数里用差分数组验证前 mid 个订单是否会爆仓。
P1314 [NOIP2011 提高] 聪明的质监员 🔵 提高+/省选- 二分+前缀和。二分参数 WWcheck 里需要快速计算区间校验值,必须上前缀和优化,否则 TLE。
P1902 刺杀大使 🟡 普及/提高- 二分+BFS/DFS。二分“伤害值上限”,check 函数里跑一次 BFS 判断能否在不接触大于该伤害的格子的前提下到达终点。
P4343 [SHOI2015] 自动刷题机 双重二分。切掉的代码行数随 nn 单调变化,可能需要求 nn 的最小值和最大值,需要写两个二分或者特判。

📝 教练的实战锦囊

  1. 模板选择: 整数二分最容易写成死循环。建议固定背诵一种风格,比如我最推荐的 “记录答案法”

    int l = 1, r = 1e9, ans = -1;
    while (l <= r) {          // 只有 l > r 才停止,不会死循环
        int mid = l + (r - l) / 2; // 防溢出写法
        if (check(mid)) {
            ans = mid;        // 先记录这一个可行解
            // 比如要找“最小”,那就尝试往左找
            r = mid - 1;      
        } else {
            l = mid + 1;
        }
    }
    cout << ans;
    

    这种写法不需要纠结 mid 还是 mid+1 还是 mid-1,逻辑最清晰。

  2. 单调性判断: 做题前先问自己:“如果答案是 X 行得通,那么 X+1 是不是一定也行(或一定不行)?” 如果是,恭喜你,这题就是二分。

  3. Check 函数的复杂度: 二分的总复杂度是 O(log(范围)×check的复杂度)O(\log(\text{范围}) \times \text{check的复杂度})。 如果你的 check 函数写成了 O(N2)O(N^2),总复杂度就炸了。通常 check 应该是 O(N)O(N) 或者 O(1)O(1)

从 P2249 开始热身,然后死磕 P2678 跳石头P1873 砍树,这两题悟透了,普及组的二分就没问题了!


你好!我是你的 OI 教练。

贪心算法 (Greedy Algorithm) 是 OI 中最迷人也最危险的算法。

  • 迷人:代码通常很短,核心逻辑往往就是一行排序 (sort) 或者一个优先队列。
  • 危险:证明很难。很多时候你觉得“这样做肯定对”,交上去却 WA 了,因为局部最优不等于全局最优。

为了帮你建立正确的“贪心直觉”,我为你整理了一份循序渐进的洛谷贪心题单。


第一阶段:入门贪心(简单排序)

目标:理解“排序”在贪心里的作用。这一阶段的题目通常直觉很准。

题目编号 题目名称 难度 训练重点
P1223 排队接水 🟠 普及- 经典入门。让接水快的人先接,后面的人等待时间就少。考察排序不等式思想。
P1803 凌乱的yyy / 线段覆盖 必刷! 区间贪心鼻祖。参加最多的比赛 \to结束时间早的先选。
P2240 【深基12.例1】部分背包问题 性价比。物品可以分割,那就优先拿“单价最高”的,直到装满。
P1208 [USACO1.3] 混合牛奶 同上。按牛奶单价排序,便宜的先买。
P3817 小A的糖果 线性扫描。相邻两堆不能超过 xx,从左往右扫,这就决定了只需要看相邻项,无需全局排序。

第二阶段:进阶贪心(结构与策略)

目标:不仅是简单的数组排序,可能需要用到优先队列 (Priority Queue) 或者特殊的策略。

题目编号 题目名称 难度 训练重点
P1090 [NOIP2004 提高] 合并果子 🟡 普及/提高- 哈夫曼树思想。必用 priority_queue(小根堆)。每次挑最小的两堆合并,消耗体力最少。
P5019 [NOIP2018 普及] 铺设道路 🟠 普及- 思维。不需要复杂的算法,通过观察发现:当前坑比前一个深,就需要多填几次。
P1106 删数问题 🟡 普及/提高- 字符串贪心。不要直接排序!要在保持相对顺序的情况下,让高位的数字尽可能小。这是一个经典的单调栈或贪心思路。
P1031 [NOIP2002 提高] 均分纸牌 流动思想。从左往右推,不够就问右边要,多了就给右边。
P1478 陶陶摘苹果(升级版) 🟠 普及- 带限制的贪心。力气有限,要想摘得多,就先摘花费力气最小的(而不是高度最低的)。

第三阶段:数学贪心(推公式)

目标:不能凭直觉,需要用邻项交换法推导出排序规则。这是提高组的高频考点。

题目编号 题目名称 难度 训练重点
P1080 [NOIP2012 提高] 国王游戏 🔵 提高+/省选- 神题。经典的“微扰法”证明。结论是按 left×rightleft \times right 升序排列。注意:这题需要高精度
P3269 [JLOI2016] 字符串覆盖 字符串+贪心。虽然带点 DP 味道,但很多字符串问题核心策略是贪心。
P1056 [NOIP2008 普及] 排座椅 🟡 普及/提高- 计数+排序。统计每行每列能隔开多少对同学,按“贡献度”排序。

第四阶段:反悔贪心(Regret Greedy)

目标:掌握一种高级技巧——“先选上,如果后面遇到更好的,就把前面最差的踢掉”。通常配合优先队列

题目编号 题目名称 难度 训练重点
P2949 [USACO09OPEN] Work Scheduling G 🔵 提高+/省选- 反悔贪心入门。按照截止时间排序,能做就做;不能做时,看已选的任务里有没有价值比当前低且耗时的,把它踢掉换成当前的。
P4053 [JSOI2007] 建筑抢修 反悔贪心经典。按截止时间排序,维护一个大根堆记录已修建筑的耗时。如果修不完,就放弃那个最耗时的。
P2123 皇后游戏 国王游戏的加强版。推导排序不等式更加复杂,不仅涉及乘积,还涉及 min/max\min/\max 的讨论。

📝 教练的实战锦囊

  1. 贪心 vs DP: 这是考场上最纠结的。

    • 如果状态之间没有后效性(现在的选择不会限制未来的选择空间,或者我们只在乎当前最优能导致全局最优),用贪心。
    • 如果需要回溯、需要考虑多种组合才能决定哪种最好,通常是 DP。
    • 判断技巧:如果 NN 很大(10510^5),通常是贪心(O(NlogN)O(N \log N));如果 NN 较小(10001000),可能是 O(N2)O(N^2) 的 DP。
  2. 验证贪心策略: 在纸上画几个反例。

    • 比如“合并果子”,你可能会想“先合并最大的”。试一下:1, 1, 100, 100。
    • 如果先合大的:(100+100)=200, (1+1)=2, (200+2)=202。总代价 200+2+202=404。
    • 如果先合小的:(1+1)=2, (2+100)=102, (102+100)=202。总代价 2+102+202=306。
    • 明显小的更优。举反例是验证贪心最快的方法。
  3. 不要迷信排序: 虽然 80% 的贪心都需要排序,但怎么排是关键。是按 aa 排?按 bb 排?还是按 a+ba+ba×ba \times b 排? 遇到这种双参数的,一定要推导一下。

P1223P1803 开始,这两题是贪心的灵魂!加油!


回到前缀与差分,我看公式好像和数列裂项求和有关?

太犀利了!你的数学直觉非常敏锐!

你完全说到了点子上。差分与前缀和,在本质上就是数学数列中**“裂项相消求和”(Telescoping Sum)的编程实现。甚至更进一步说,它们就是微积分**在离散数学(整数数组)中的体现。

我来给你详细拆解一下这个数学本质,理解了这一点,你对这套算法的记忆会从“死记硬背”变成“理所当然”。


一、 差分还原 = 裂项相消

我们先看差分。 假设原数组是 aa,差分数组是 DD。 定义:D[i]=a[i]a[i1]D[i] = a[i] - a[i-1]

当我们想要把差分数组 DD 还原回原数组 a[k]a[k] 时,我们需要做前缀和。 公式是:

a[k]=i=1kD[i]a[k] = \sum_{i=1}^{k} D[i]

让我们展开这个求和过程,看看发生了什么:

$$\begin{aligned} \sum_{i=1}^{k} D[i] &= D[1] + D[2] + D[3] + \dots + D[k] \\ &= (a[1] - a[0]) + (a[2] - a[1]) + (a[3] - a[2]) + \dots + (a[k] - a[k-1]) \end{aligned} $$

发现了吗?中间项全部抵消了(裂项相消)!

  • a[1]a[1]a[1]-a[1] 抵消
  • a[2]a[2]a[2]-a[2] 抵消
  • ...
  • a[k1]a[k-1]a[k1]-a[k-1] 抵消

最终结果:

=a[k]a[0]= a[k] - a[0]

(通常在 OI 中我们设 a[0]=0a[0]=0,所以结果就是 a[k]a[k]

这就是标准的裂项求和。所以说,“差分数组的前缀和就是原数组”,这个结论的数学证明就是裂项相消。


二、 前缀和求区间 = 牛顿-莱布尼茨公式

我们再看前缀和。 假设原数组是 aa,前缀和数组是 SS。 定义:S[i]=j=1ia[j]S[i] = \sum_{j=1}^{i} a[j]

当我们要求区间 [L,R][L, R] 的和时,公式是:

Sum(L,R)=S[R]S[L1]\text{Sum}(L, R) = S[R] - S[L-1]

这其实对应了微积分里的牛顿-莱布尼茨公式(微积分基本定理):

LRf(x)dx=F(R)F(L)\int_{L}^{R} f(x) dx = F(R) - F(L)

在离散的世界里:

  1. 原数组 aa 就像是 函数 f(x)f(x)
  2. 前缀和数组 SS 就像是 原函数 F(x)F(x)(不定积分)。
  3. 区间求和 就像是 定积分

你求区间和,不需要把中间的数一个个加起来(就像积分不需要画无数个小矩形去累加),只需要用端点的原函数值相减即可。


三、 差分修改 = 导数与阶跃

为什么差分能 O(1)O(1) 修改区间?这也和数学有关。

想象原数组 aa 是一条曲线。 差分数组 DD 其实就是 aa 的“导数”(变化率)。

  • 如果 aa 是一条平的直线(全相等),那 DD(导数)全是 0。
  • 如果我们想把区间 [L,R][L, R] 整体抬高 vv
    • LL 这个点,图像突然跳上去了(突变)。所以导数 D[L]D[L] 瞬间增加 vv
    • LLRR 之间,图像虽然变高了,但**平坦程度(斜率)**没变!所以中间的差分(导数)不需要改。
    • R+1R+1 这个点,图像又掉回去了。所以导数 D[R+1]D[R+1] 瞬间减少 vv

这在信号处理里叫**“脉冲函数”“阶跃响应”**。


四、 总结:这三者的关系图谱

在 OI 中,你可以建立这样一个极其清晰的数学直觉:

数学概念 数组概念 操作 / 意义
函数 f(x)f(x) 原数组 a[i]a[i] 原始数据
导数 f(x)f'(x) 差分数组 D[i]D[i] 记录变化量,裂项项
积分 f(x)\int f(x) 前缀和数组 S[i]S[i] 记录累加量,原函数
  • 前缀和运算 \approx 积分运算 (把裂项累加起来)
  • 差分运算 \approx 求导运算 (把累加项拆开)

它们是互逆的。

你的观察非常到位! 记住“裂项相消”这个图景,以后写差分代码时(特别是差分还原那一刻),你脑海里浮现的就不再是枯燥的代码,而是那个漂亮的、首尾相消的数学公式。加油!

章节 1. 桶

开放

题目 尝试 AC 难度
3821   直播获奖 1 1 10
712   校门外的树 0 0 (无)

章节 2. 前缀与差分

开放

题目 尝试 AC 难度
1   A+B 输入输出练习I 0 0 (无)

章节 3. 二分查找

开放

题目 尝试 AC 难度
373   算法9-1:静态表的顺序查找 4 1 10
374   算法9-2:有序表的折半查找 0 0 (无)

章节 4. 贪心

开放

题目 尝试 AC 难度
807   配对 0 0 (无)