题目来源:改编自 洛谷 P1102 A-B 数对。本 Lab 使用课程自定义输入输出协议和独立测试,不复制来源站点的代码或测试。
学习目标
前置知识与环境
先阅读第 8.1 节 基础查找与折半查找的"折半查找前提"与"折半查找算法"两节:本 Lab 的序列初始无序,需要先排序再折半,正好对应"预处理 + 多次查找"的摊还视角。
在 Lab 目录执行 make doctor 检查环境;未安装 Make 时可在仓库根使用 pnpm 兜底命令(见下文"运行")。
任务
给定 n 个整数与目标差 c,统计满足 a_i − a_j = c 的有序下标对 (i, j) 的个数。约定:
- 不同位置算不同数对,即使两处数值相同;
i与j可以相同——因此c = 0时每个下标与自己恰好构成一对(全相同时答案为 )。
输入格式
- 第一行:两个整数
n和c; - 第二行:
n个整数(顺序即下标顺序,下标从 1 开始)。
输出格式
一行一个整数:满足 a_i − a_j = c 的有序数对个数。答案可达
数据范围与限制
| 项目 | 范围 |
|---|---|
元素个数 n | 1 ≤ n ≤ 2×10⁵ |
目标差 c | |c| ≤ 10⁹ |
| 元素取值 | |元素| ≤ 10⁹ |
| 时间复杂度要求 | 排序 + n 次二分, |
| 输出上限 | 1 MB(单个 64 位整数,默认上限足够) |
正常、边界与错误情况
- 正常:重复值按位置计数,一个 A 位置可以匹配多个等值的 B 位置;
- 边界:
c = 0且全部元素相同(答案 );c为负数;元素为负数;a[i] + c可达 ±2×10⁹,超出int范围; - 无解:不存在任何满足条件的数对时输出
0。
样例
样例 1
4 1
1 1 2 33A − B = 1 的有序数对:A = 2 可配两个不同位置的 B = 1(2 对),A = 3 可配 B = 2(1 对),合计 3。
样例 2
6 -2
-5 -3 -3 -1 1 16A = B − 2:两个位置的 B = −3 各配到 A = −5(2 对);B = −1 配到两个位置的 A = −3(2 对);两个位置的 B = 1 各配到 A = −1(2 对);合计 6。
实现提示
把 A − B = c 改写成 A = B + c:排序一份副本,对序列中每个值 x(枚举的是 B),用二分统计 x + c 在有序数组中的出现次数,即 upper_bound(x + c) − lower_bound(x + c),累加到 64 位答案里。
两个易错点:
x + c最大可达 ,超出int表示范围,目标值要用long long存放;- 累加器必须是
long long——c = 0且全相同时答案为 ,int会静默溢出。
按值建散列表计数能做到平均
运行
make run
# 免 Make 兜底:在仓库根执行
pnpm lab:run -- labs/chapter-08/exercise/E-08-04-count-diff-pairs需要单用例或严格评分时使用 make run CASE=005-stress-2e5 与 make score;也可用 pnpm lab:score -- labs/chapter-08/exercise/E-08-04-count-diff-pairs 兜底。
完成清单
思考与复盘
- 若把约定改成"i ≠ j(排除自身配对)",二分计数法应如何修正?
c = 0时答案从 变成什么? - "对每个值二分
x + c"枚举的是 B,"对每个值二分x − c"枚举的是 A——为什么两种数法结果一定相同? - 排序二分的
与散列表的平均 ,在什么前提下后者才真正更优?有序数组在"多次查询不同 c"的场景下还有什么好处?