例题给全套:题面、样例、参考代码、以及一步步的讲解。先自己读题想一遍, 再看代码,最后对着讲解核一遍自己想的对不对。
练习只给题面、样例和一句提示,答案是折叠起来的 —— 请先自己写一版跑通了再展开。写不出来也别直接看答案,先回速查那一组找找。
每份代码都能直接点运行:输入框里已经填好了样例输入, 把它改一改再跑,是这一页最值钱的用法。
先把地基摆一遍
要记的只有一件事:sort 不知道「谁该排前面」,那是你告诉它的。不告诉它,它就按 < 排(数字从小到大、字符串按字典序)。
★ 它要的两个参数是「从哪儿开始」和「到哪儿结束」,而结束那个是「最后一个的下一格」。所以整个 vector 是 a.begin(), a.end(),裸数组是 arr, arr + n;只排前 5 个就是 a.begin(), a.begin() + 5。这个「左闭右开」的习惯贯穿整个 STL。
⚠ 自己写比较规则时,函数返回的是「a 该排在 b 前面吗」,不是「谁更大」。想降序就写 return a > b; —— 读成「a 比 b 大时,a 排前面」。
★★ 而这里有一条会让程序直接崩溃的规矩:比较函数里绝对不能写 >= 或 <=。sort 要求「两个元素一样时必须返回 false」,写成 >= 时 a 和 b 相等也返回 true,sort 内部就会越过数组边界一直找下去。⚠ 而小数据上它常常正好不崩,第 10 章量过这件事。
例题(2 道)
每道都给全套:题面 → 样例 → 参考代码 → 一步步的讲解。
例题 ① 成绩排名
有 n 个学生,每人一个名字和一个分数。请按分数从高到低输出;分数相同的,按输入顺序(先输入的排前面)。
n(1 ≤ n ≤ 10⁵);接下来 n 行,每行一个不含空格的名字和一个整数分数。n 行,每行一个名字和一个分数。输入
4 ann 90 bob 85 cid 90 dan 70
输出
ann 90 cid 90 bob 85 dan 70
两个坑:① 名字和分数要一起排序,怎么打包?② 题面说「分数相同按输入顺序」—— 而 sort 是不稳定的,它不保证并列的两个谁在前。⇒ 光按分数排够不够?
// 例题 ①「成绩排名」—— 排序与比较器组//// 三件事一次学完:struct 打包、lambda 当比较规则、**并列时怎么办**。// ⚠ 题面要求「分数相同按输入顺序」,而 sort 是**不稳定**的 ——// 不能指望它自己保住输入顺序,得**把输入序号也写进比较规则里**。
#include <bits/stdc++.h>using namespace std;
struct Student { // ⚠ 花括号后面这个分号不能漏 string name; int score; int id; // 输入序号 —— 就是为了并列时能分出高下};
int main() { int n; cin >> n;
vector<Student> a(n); for (int i = 0; i < n; i++) { cin >> a[i].name >> a[i].score; a[i].id = i; }
sort(a.begin(), a.end(), [](const Student &x, const Student &y) { // 返回的是「x 该排在 y 前面吗」,不是「谁更大」 if (x.score != y.score) return x.score > y.score; // 分数高的在前 return x.id < y.id; // 分数一样:先来的在前 // ⚠ 绝对不能写成 return x.score >= y.score; // 相等时也说「x 排前面」,sort 会一直往前找、越过数组边界。 });
for (const Student &s : a) cout << s.name << ' ' << s.score << "\n"; return 0;}点「运行 ▶」看结果
struct Student { string name; int score; int id; };——把要一起动的几个量打包成一个新类型。⚠ 花括号后面那个分号不能漏。- 那个
id是专门加的:读入时记下「我是第几个」,排序时用它来给并列的人分高下。⇒ 这比换成stable_sort更稳,因为它把「凭什么这么排」写在了代码里,而不是依赖某个库的性质。 - 比较规则用 lambda 就地写:
[](const Student &x, const Student &y) { … }。[]是它的开头标记,后面是参数表和函数体,整个东西直接塞进sort的第三个位置。 - ⚠ 返回的是「x 该排在 y 前面吗」,不是「谁更大」。所以「分数高的在前」写成
return x.score > y.score;。 - ⚠⚠ 而这一行绝对不能写成
return x.score >= y.score;:sort 要求「两个元素一样时必须返回 false」,写成>=时它会一直往前找、越过数组边界 —— 表现是 Segmentation fault,而小数据上常常正好不崩。 - 并列时要再比一个字段:
if (x.score != y.score) return x.score > y.score; return x.id < y.id;——先分主次,主的分不出高下再看次的。这是所有「多关键字排序」的通用形状。
这道题的四行比较规则,是本书第 10、19、20、34 章每一章都要写一遍的东西。
用到的写法:struct [](const T &a, const T &b) { return ...; } 比较器里不许写 >= sort(a.begin(), a.end()) stable_sort
例题 ② 按右端点排序
给 n 个区间 [l, r],请按右端点从小到大输出;右端点相同的按左端点从小到大。
★ 这不是随便编的题:第 19、20 章的区间贪心,第一步永远是「按右端点排序」。
n(1 ≤ n ≤ 10⁵);接下来 n 行,每行两个整数 l、r。n 行,每行两个整数 l、r。输入
4 1 5 2 3 0 3 4 9
输出
0 3 2 3 1 5 4 9
0 3 在 2 3 前面。这道题一行比较器都不用写。想想 pair 自带的规矩是什么,然后决定「哪个量该放在 first」。
// 例题 ②「按右端点排序」—— 排序与比较器组//// 第 19、20 章的区间贪心,第一步永远是「按右端点排序」。// 这里不用写比较器:pair 自带的规矩就是「先比 first,first 一样再比 second」// ⇒ 只要把要排的那个量放在 first 就行。
#include <bits/stdc++.h>using namespace std;
int main() { int n; cin >> n;
vector<pair<int, int>> a(n); // 存成 (右端点, 左端点) for (int i = 0; i < n; i++) { int l, r; cin >> l >> r; a[i] = {r, l}; // ⚠ 谁放 first 是**约定**不是类型 —— 放反了编译器一声不吭 }
sort(a.begin(), a.end()); // 按 r 升序,r 相同按 l 升序
for (const auto &p : a) cout << p.second << ' ' << p.first << "\n"; return 0;}点「运行 ▶」看结果
pair<int,int>自带的比较规则正好是:先比first,first一样再比second。⇒ 「按 r 排、r 同按 l 排」和这条规矩一字不差。- 所以只要把 r 放进
first:a[i] = {r, l};,然后sort(a.begin(), a.end())就完事了。 - ⚠ 而这正是 pair 最容易咬人的地方:谁放
first是一个约定,不是类型的一部分。放反了编译器一声不吭,程序照跑,只是排出来的顺序不对。本书第 29 章存图用(邻居, 权)、第 32 章堆里用(距离, 编号)—— 顺序正好相反。 - 输出时记得换回来:
p.second是 l、p.first是 r。 sort的两个参数是「从哪儿开始」和「到哪儿结束」,而结束那个是最后一个的下一格(a.end())。这个「左闭右开」的习惯贯穿整个 STL。
能不写比较器就别写 —— 少一处能写错的地方。⚠ 但代价是「谁是 first」这件事只活在你脑子里,所以写注释说清楚。
练习(3 道)
答案折叠着,先自己写一版。
练习 ① 从大到小
给 n 个整数,从大到小输出。
n(1 ≤ n ≤ 10⁵);第二行 n 个整数。n 个整数,用空格隔开,行末不留空格。输入
5 3 1 4 1 5
输出
5 4 3 1 1
两条路都对:给 sort 传第三个参数,或者升序排完再翻过来。⚠ 用前者的话,那个参数后面的一对空括号不能少。
用到的写法:sort(a.begin(), a.end(), greater<int>()) sort(a.begin(), a.end())
练习 ② 中位数
给 n 个整数(n 保证是奇数),输出排好序之后正中间的那个数。
n(1 ≤ n ≤ 10⁵);第二行 n 个整数。输入
5 7 1 9 3 5
输出
5
7 1 9 3 5 排好是 1 3 5 7 9,正中间是 5。排完序之后,「正中间」的下标是几?拿 n = 5 在纸上数一遍就知道了。
⚠ 顺带想一想:如果 n 可能是偶数,题目该怎么定义中位数?(这就是第 37 章那道「对顶堆」的入口。)
用到的写法:sort(a.begin(), a.end())
练习 ③ 按绝对值排序
给 n 个整数,按绝对值从小到大排序输出;绝对值相同的,小的排前面。
n(1 ≤ n ≤ 10⁵);第二行 n 个整数。n 个整数,用空格隔开,行末不留空格。输入
5 3 -1 -4 1 5
输出
-1 1 3 -4 5
-1 和 1 的绝对值都是 1 ⇒ 按第二条规矩,-1 排前面。比较规则不一定要直接比元素本身,比「元素算出来的某个量」也行。⚠ 而题面那句「绝对值相同时小的排前面」是必须写进比较器的 —— 想想不写会怎样。
这道题把「多关键字」的形状又走了一遍:先比主的,主的分不出高下再比次的。