一个学生的「姓名 + 语文 + 数学 + 英语」本来要开四个数组,今天学会用一个 struct 全装下
| 0 - 25 min | 看懂「为什么要结构体」——五个平行数组的痛点(第 1 节) |
| 25 - 55 min | 定义语法 + 结构体数组 + 成员访问(第 2、3 节) |
| 55 - 80 min | 今天的核心套路:读入 → 算派生字段 → 排序 → 输出(第 4 节) |
| 80 - 180 min | 做 4 道题,每题限时 25 分钟 |
| 180 - 210 min | 对照本文题解,把没做出来的补掉 + 做自测 |
Day 10 你学了函数:把一段逻辑打包成有名字的一块。今天要学的是另一种「打包」—— 把几个相关的数据变量打包成一个整体。它们思想是一样的:给一堆东西起个名字,让代码更像人话。
昨天写 double dis(double x1,double y1,double x2,double y2) 时,参数已经开始变多了吧?
今天的结构体正是用来解决「参数/数据太多,散落一地」这个问题的。
老师给你 3 个同学的信息,要你按总分从高到低排序输出。信息长这样:
senpai 114 51 4 lxl 114 10 23 fafa 51 42 60
每行是:姓名、语文、数学、英语。现在问题来了——用你已经学过的知识,怎么存?
string name[1005]; // 姓名放一个数组 int chinese[1005], math[1005], english[1005]; // 三科成绩各放一个数组 int total[1005]; // 总分再放一个
看着还行?那你试试按总分排序:
// 想交换第 i 个和第 j 个同学? swap(name[i], name[j]); swap(chinese[i], chinese[j]); swap(math[i], math[j]); swap(english[i], english[j]); swap(total[i], total[j]); // 少写一个就全乱了!
1. 一交换就要写 5 行,漏掉任何一个数组,这个同学的信息就「串味」了——姓名还是小明的,成绩变成小红的了。
2. 数不清:以后字段变成 10 个(还要加身高、班级、准考证号),交换就要写 10 行。
3. 没法把「一个同学」当整体传给函数。你想写一个 printStudent(i),函数签名得写成
printStudent(string, int, int, int, int),五个参数排着队,谁是谁全靠背。
把「本来就属于同一个东西的若干变量」,绑成一个整体,给它们起一个共同的名字。
「小明的姓名、小明的语文、小明的数学、小明的英语」这四样东西,本来就是一伙的, 凭什么要拆到四个数组里去?让它们待在一起,交换的时候整包一起换,多省事。
平行数组就像把一个人的身份证、学生证、银行卡分三个抽屉放——用的时候要去三个抽屉翻,搬家还要三个抽屉一起搬。
结构体就像一个文件袋,上面写着「小明」,里面装着他的所有材料。要交材料就整个袋子递过去, 要换人就整个袋子换掉。不存在「只换了半个袋子」这种事。
struct Student { // struct 关键字 + 结构体名字(自己起)
string name; // 成员 1:姓名
int chinese; // 成员 2:语文
int math; // 成员 3:数学
int english; // 成员 4:英语
int total; // 成员 5:总分(自己算出来的,叫"派生字段")
}; // ★★★ 这个分号千万别丢!★★★
① 末尾的分号 }; 必须有。这是 C++ 里极少数「花括号后面还要跟分号」的地方。
忘了它,编译器会报一堆莫名其妙的错(往往报在后面好几行,让你找不到北)。
② 结构体名字首字母习惯大写(Student、Person、Point),
变量名小写(a、p)。这不是语法要求,但是全世界的习惯,照着写别人一眼就懂。
③ 定义只是「画图纸」,不占内存。struct Student{...}; 只是告诉编译器「以后会有这么一种东西」,
真正占内存的是下面声明的变量。
把结构体名字当成一种新的「类型」,像用 int 一样用它:
Student a; // 一个学生 Student b[1005]; // 1005 个学生 —— 这叫「结构体数组」 cin >> a.name >> a.chinese >> a.math >> a.english; // 输入 a.total = a.chinese + a.math + a.english; // 算总分 cout << a.name << " " << a.total << endl; // 输出
a.name 读作「a 的 name」,意思就是从 a 这个文件袋里,取出 name 这一份材料。
这个点号 . 叫成员访问运算符。它是结构体的标志——看到 ×××.×××,
就知道前面是个结构体变量,后面是它的一个成员。
关键区别:a 是一个整体(一个学生),a.name 是一个普通的 string 变量。
你不能 cout << a,但可以 cout << a.name。
| 成员类型 | 例子 | 说明 |
|---|---|---|
| 基本类型 | int age; double score; | 最常用 |
| string | string name; | 今天的 P5740 / P1104 都要用 |
| 数组 | int score[5]; | 固定几科成绩时很方便 |
| 另一个结构体 | Date birthday; | 结构体可以套娃 |
| 构造函数 | Student(){ total = 0; } | 进阶用法,今天用不到 |
今天四道题只用到前两种:string 和 int。等遇到了再回来看这张表。
几乎每道结构体题都是「n 个结构体,排个序,输出几个」。所以结构体数组是最常用的形态:
struct Student {
string name;
int chinese, math, english, total;
} a[1005]; // ★ 定义的同时顺便把数组也声明了
// 也可以分开写:
struct Student { ... };
Student a[1005]; // 效果完全一样
Day 6 讲数组时你就知道了:数组开在 main 外面(全局)会自动清零。 结构体数组开在全局也一样,所有成员自动是 0 / 空字符串,省事又安全。今天四道题我都开在全局。
结构体数组的每个元素都是一个完整的结构体,所以访问方式是「下标 + 点 + 成员」:
a[i].name // 第 i 个学生的姓名 a[i].total // 第 i 个学生的总分 a[i].chinese // 第 i 个学生的语文成绩
正确:a[i].name ❌ 错误:a.name[i](除非 name 真的定义成了数组)
记忆口诀:先锁定「第几个」,再取「哪一项」。先 a[i] 找到这个学生,
再从里面 .name 拿姓名。
int n;
cin >> n;
for(int i = 0; i < n; i++){
cin >> a[i].name >> a[i].chinese >> a[i].math >> a[i].english;
a[i].total = a[i].chinese + a[i].math + a[i].english; // ★ 顺手把总分算出来
}
题目只给你原始数据(三科成绩),但排序/比较需要的是加工过的数据(总分)。
所以读入的时候顺手把总分算出来存进结构体,后面排序时直接用 a[i].total,
不用每次都现算。算一次、用很多次——这就是「派生字段」的价值。
对 int 来说,「谁大谁小」是天然确定的。但两个 Student 谁应该排在前面?
比总分?比语文?比学号?编译器不知道,必须你来告诉它。
昨天的 int 数组排序是这样:
sort(a, a + n); // 从小到大,编译器懂
结构体要额外给一个「比较函数」当裁判:
bool cmp(const Student &x, const Student &y){
return x.total > y.total; // 返回 true:x 应该排在 y 前面
}
sort(a, a + n, cmp); // 把裁判交给 sort
cmp(x, y) 返回 true,意思就是「x 排在 y 前面」。
所以 return x.total > y.total; = 「总分大的排前面」= 降序。
想升序就写 return x.total < y.total;。
别去背「大于还是小于」,每次写的时候问自己一句:「我想让谁在前面?」
很多题目不只一个排序条件,比如「总分高的在前;总分一样时语文高的在前;都一样时学号小的在前」。 写法就是一串 if,一层一层筛:
bool cmp(const Student &x, const Student &y){
if(x.total != y.total) return x.total > y.total; // ① 总分降序
if(x.chinese != y.chinese) return x.chinese > y.chinese; // ② 语文降序
return x.id < y.id; // ③ 学号升序
}
!=
if(x.total != y.total) 的意思是「总分不一样才轮到我做决定」。
一样的话就跳过这一行,交给下一个条件。
如果写成 if(x.total > y.total) return true; 就错了——
总分相等时会直接跳到 return x.id < y.id;,
于是「总分小的」也会因为学号小而被判为排在前面。这是新手最常犯的错。
const Student &x这是 C++ 里传结构体的标准姿势,两个符号各管一件事:
| 写法 | 含义 | 好处 |
|---|---|---|
Student x | 把整个结构体复制一份传进来 | ❌ 每个元素都复制,慢 |
Student &x | 传引用(别名),不复制 | 快 |
const Student &x | 传引用 + 不许改 | ✅ 又快又安全,标准写法 |
今天的 cmp 请统一写 const Student &x。等以后结构体里字段变多了,这个习惯能帮你省下大量时间。
下面每道题的代码我都用 g++ -std=c++14 -O2 -Wall 实际编译并跑过官方样例,输出是你看到的真实结果。
题意:给出 N 个同学的姓名和三科成绩,找出总分最高的那位, 输出他的姓名和原始三科成绩(注意:不是输出总分,是输出原始的三科分数)。 如果有多个人总分并列最高,输出输入靠前的那位。
1 ≤ N ≤ 1000;姓名是长度不超过 8 的纯小写字母字符串;三科成绩都是不超过 150 的自然数。
成绩上限 150,三科总分最大 450,int 完全够。
这题是结构体的「Hello World」,用打擂台的思路就够了,不需要真的排序:
best = 0)。「并列时输出靠前的」怎么实现?关键在第 3 步的「严格大于」——
用 > 而不是 >=。总分相等时擂台不换人,自然就保留了靠前的那位。
3 senpai 114 51 4 lxl 114 10 23 fafa 51 42 60
senpai 114 51 4
三个人总分:senpai = 114+51+4 = 169,lxl = 114+10+23 = 147,fafa = 51+42+60 = 153。最高的是 senpai。
#include <iostream> #include <string> using namespace std; // 结构体:把「一个同学的全部信息」打包成一个整体 struct Student { string name; // 姓名 int chinese, math, english; // 三科成绩 int total; // 派生字段:总分(读入之后算出来) } a[1005]; // 结构体数组:一次声明 1005 个同学 int main(){ int n; cin >> n; for(int i = 0; i < n; i++){ // 结构体数组的输入:a[i] 是一个整体,用 a[i].字段 逐个点名 cin >> a[i].name >> a[i].chinese >> a[i].math >> a[i].english; a[i].total = a[i].chinese + a[i].math + a[i].english; } int best = 0; // best 记住「目前最厉害的同学」的下标 for(int i = 1; i < n; i++){ if(a[i].total > a[best].total){ // ★ 用 > 不用 >=:并列时保留靠前的 best = i; } } cout << a[best].name << " " << a[best].chinese << " " << a[best].math << " " << a[best].english << endl; return 0; }
官方样例 → 输出 senpai 114 51 4 完全一致
自造并列测试(aaa 和 bbb 都是满分 100+100+100,aaa 在前)→ 输出 aaa 100 100 100 正确保留了靠前的同学
边界 N = 1(zoe 150 150 150)→ 输出 zoe 150 150 150 正确
>=:并列时后面的会顶掉前面的,违反了「输出靠前的那位」。}; 分号忘了写。string,要用 #include <string>(虽然很多编译器不写也能过,
但规范起见写上)。题意:n 个学生,每人有语文、数学、英语三科成绩。按下面的规则排出一个唯一确定的名次,然后输出前 5 名的学号和总分。
排序规则:① 总分从高到低;② 总分相同时语文从高到低;③ 总分和语文都相同时学号小的在前。
5 ≤ n ≤ 300;每科成绩在 0 到 100 之间;学号按输入顺序编号为 1 ~ n。n 至少是 5, 所以一定有 5 个人可以输出。
这是结构体 + 多关键字排序的标准题。
a[i].id = i;)——排序会把顺序打乱,学号必须跟着人走。sort 之后,前 5 个就是获奖的同学,输出 id 和 total。如果只把成绩存进数组,排序后你就不知道哪个成绩是谁的了。
学号是「身份标签」,必须和本人的数据绑在一起,一起被搬来搬去。这正是结构体最擅长的事——
要是用平行数组,你就得开一个 id[] 数组,然后在 cmp 里手动同步交换,一不留神就对不上了。
题面里举了例子:7 号总分 279、5 号总分 279,但 7 号语文更高,所以必须输出 7 在前。 如果你只按总分排,两人顺序可能随机,就会 WA。三个关键字一个都不能少。
6 90 67 80 87 66 91 78 89 91 88 99 77 67 89 64 78 89 98
6 265 4 264 3 258 2 244 1 237
手算验证:1 号 237、2 号 244、3 号 258、4 号 264、5 号 220、6 号 265。按总分降序正好是 6、4、3、2、1。
#include <iostream>
#include <algorithm>
using namespace std;
struct Student {
int id; // 学号
int chinese, math, english; // 三科成绩
int total; // 总分
} a[305];
// 比较函数:返回 true 表示 x 应该排在 y 前面
bool cmp(const Student &x, const Student &y){
if(x.total != y.total) return x.total > y.total; // ① 总分高的在前
if(x.chinese != y.chinese) return x.chinese > y.chinese; // ② 语文高的在前
return x.id < y.id; // ③ 学号小的在前
}
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i++){
a[i].id = i; // ★ 学号 = 输入行号,必须存下来
cin >> a[i].chinese >> a[i].math >> a[i].english;
a[i].total = a[i].chinese + a[i].math + a[i].english;
}
// 注意:数据存在 a[1] ~ a[n],所以是 sort(a+1, a+n+1, cmp)
sort(a + 1, a + n + 1, cmp);
for(int i = 1; i <= 5; i++){
cout << a[i].id << " " << a[i].total << endl;
}
return 0;
}
sort(a + 1, a + n + 1, cmp) 这个 +1 别写错
sort 的范围是左闭右开 [first, last)。数据在 a[1] 到 a[n],
所以要写到「最后一个元素的下一个位置」a + n + 1。
写成 sort(a + 1, a + n, cmp) 的话,最后一个同学压根没参与排序,
样例可能还过,但隐藏数据必挂。
官方样例 1 → 输出 6 265 / 4 264 / 3 258 / 2 244 / 1 237 完全一致
官方样例 2(8 人,含两组同分需要比较语文/学号)→ 输出 8 265 / 2 264 / 6 264 / 1 258 / 5 258 完全一致
样例 2 里 2 号和 6 号都是 264 分、语文都是 88 分 → 按学号升序输出 2、6; 1 号和 5 号都是 258 分,1 号语文 80 > 5 号语文 78 → 1 在前。三个关键字全部生效。
if(x.total != y.total) 写成 if(x.total > y.total),
会让总分相等的人错误地按学号重排。题意:给出 n 个人的姓名、出生年、月、日,按年龄从大到小输出姓名。 如果两个人年龄相同(年月日完全一样),输入靠后的同学先输出。
1 < n < 100;姓名长度 1 ≤ |s| < 20;保证年月日实际存在,年份在 [1960, 2020] 之间。
n 很小(最多 99 人),所以随便怎么写都不会超时——这题考的是「字段顺序」而不是效率。
先想清楚一件事:「年龄大」等价于「出生日期早」。所以「年龄从大到小」就是「出生日期从小到大」。
日期怎么比大小?就是看日历的习惯:先比年,年相同再比月,月相同再比日。 这正好又是三段式 cmp:
先比 y(早的在前)→ 再比 m(早的在前)→ 再比 d(早的在前)
题目要求「年龄相同则输入靠后的先输出」。注意这跟 Day 11 计划里写的「按输入顺序输出」 不太一样——是反过来的,靠后的先出来。
所以最后一层要写 return x.id > y.id;(编号大的排前面)。
把 id 当成「第四个关键字」塞进 cmp,是最稳的做法。
如果什么都不写会怎样?sort 不是稳定排序,并列元素的相对顺序是未定义的,
可能时对时错——这种「看起来能过、其实全靠运气」的代码最危险。
3 Yangchu 1992 4 23 Qiujingya 1993 10 13 Luowen 1991 8 1
Luowen Yangchu Qiujingya
Luowen 1991 年最早 → 年龄最大 → 排第一;Qiujingya 1993 年最晚 → 最小 → 排最后。
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
struct Person {
string name; // 姓名
int y, m, d; // 出生年、月、日
int id; // 输入顺序(第几个读进来的)
} a[105];
// 比较函数:返回 true 表示 x 应该排在 y 前面(年龄大的在前)
bool cmp(const Person &x, const Person &y){
if(x.y != y.y) return x.y < y.y; // 出生年份小 → 年龄大 → 排前面
if(x.m != y.m) return x.m < y.m; // 同年再比月
if(x.d != y.d) return x.d < y.d; // 同月再比日
return x.id > y.id; // ★ 生日完全相同:输入靠后的先输出
}
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i++){
a[i].id = i;
cin >> a[i].name >> a[i].y >> a[i].m >> a[i].d;
}
sort(a + 1, a + n + 1, cmp);
for(int i = 1; i <= n; i++){
cout << a[i].name << endl;
}
return 0;
}
官方样例 → 输出 Luowen / Yangchu / Qiujingya(各占一行) 完全一致
自造并列测试(4 人:A、B 都是 2000-1-1,C 是 1999-5-5,D 是 2001-12-31)
→ 输出 C / B / A / D 正确
C 年龄最大排第一;A、B 生日相同,输入靠后的 B 先输出;D 最小排最后。
y*10000+m*100+d)——这题碰巧也能过,
但更清楚的写法就是三段 if,可读性高得多。x.y < y.y,
不是 >。写下 cmp 之前先在纸上念一遍题目要求。x.id < y.id:那会变成「输入靠前的先输出」,跟题目要求正好相反。string 不是 char,输出时直接 cout << a[i].name。题意:有 N 个标了三维坐标 (x, y, z) 的点,所有点的高度 z 互不相同。 要从最低的点爬到最高的点,经过每一个点,而且每走一步高度都必须上升。 一步的距离是两点的三维欧几里得距离,求总距离(保留三位小数)。
1 ≤ N ≤ 50000,答案保证在 double 的表示范围内。
题目没有给出 x、y、z 具体的取值范围,只保证了上面两条。所以算距离时建议转成
double 再算,别让 int 溢出。
这题的巧妙之处在「把三维问题变成一维排序问题」。
题目的约束是「每一步高度 z 都必须上升」,而且「经过每一个点」。 那么攀爬路径其实已经被唯一确定了——就是把所有点按 z 从小到大排一排,依次走过去。
假设按 z 排好是 P₁ < P₂ < P₃ < … < P_N。要求每步高度都上升, 那第一站只能是最低的 P₁(从最低点出发),第二站只能是 P₂(比 P₁ 高的点里最矮的),依此类推。
高度互不相同这个条件保证了顺序不会有歧义(万一有两点一样高,就没法排先后了)。 所以这题的「排序」不是用来选最优的,而是用来还原唯一路径的。
排完序,从 p[0] 顺着算到 p[n-1],把相邻两点的三维距离加起来就行。
平面上两点距离你会了:√((x₂-x₁)² + (y₂-y₁)²)。三维就是再多加一项 z:
√( (x₂-x₁)² + (y₂-y₁)² + (z₂-z₁)² )
平方差了再开根,用 sqrt()(需要 #include <cmath>)。
5 2 2 2 1 1 1 4 4 4 3 3 3 5 5 5
6.928
按 z 排序后是 (1,1,1) (2,2,2) (3,3,3) (4,4,4) (5,5,5), 每相邻两点的距离都是 √(1+1+1) = √3 ≈ 1.7320508,四段共 ≈ 6.9282 → 保留三位小数 6.928。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
struct Point {
int x, y, z; // 三维坐标
} p[50005];
// 按高度 z 从小到大排,就得到了攀爬的先后顺序
bool cmp(const Point &a, const Point &b){
return a.z < b.z;
}
int main(){
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++){
scanf("%d %d %d", &p[i].x, &p[i].y, &p[i].z);
}
sort(p, p + n, cmp); // 排序之后,p[0] 最低,p[n-1] 最高
double ans = 0.0;
for(int i = 1; i < n; i++){
// 三维欧几里得距离:先算差值再平方,最后开根号
double dx = p[i].x - p[i-1].x;
double dy = p[i].y - p[i-1].y;
double dz = p[i].z - p[i-1].z;
ans += sqrt(dx * dx + dy * dy + dz * dz);
}
printf("%.3f\n", ans); // 保留三位小数
return 0;
}
N 最大 50000,要读 15 万个数。cin 在数据量大时比 scanf 慢一些,
用 scanf 是竞赛里的常规保险做法。
另一个办法是在 main 开头加一句 ios::sync_with_stdio(false);,
让 cin 也变快。两种都行,这题其实 cin 也能过(15 万个数不算多)。
printf("%.3f") 表示「保留 3 位小数」,会自动四舍五入;
cout 的等价写法是 cout << fixed << setprecision(3) << ans;(需要 <iomanip>)。
官方样例 → 输出 6.928 完全一致
边界 N = 1(只有一个点,不用移动)→ 输出 0.000 正确
最大规模 N = 50000(随机生成的 878 KB 输入文件)→ 输出 519450882.248,
运行耗时为毫秒级,远低于时限(复杂度 O(N log N),排序占主要时间)
int 算平方和:(p[i].x-p[i-1].x) 的平方可能超出 int 范围。
先赋给 double 变量再平方,或者写 (double) 强转。#include <cmath>:sqrt 找不到。cout << ans 会打出 6.9282 这种一堆位数,直接 WA。p[50005],别开成 1005。| 错误写法 | 后果 | 正确写法 |
|---|---|---|
| struct Student { ... } (末尾没分号) |
编译报一堆看不懂的错,报错位置常在好几行之后 | struct Student { ... }; |
| a.name[i] | 括号顺序反了,编译不过或取错东西 | a[i].name |
| if(x.total > y.total) return true; return x.id < y.id; |
总分相等时,学号小的会被误判为在前面 → 多关键字排序整体错乱 | if(x.total != y.total) return x.total > y.total; |
| sort(a+1, a+n, cmp) | 右端点是左闭右开,最后一个元素没参与排序 | sort(a+1, a+n+1, cmp) |
| 不存学号/id 就排序 | 排完不知道哪份数据是谁的 | a[i].id = i; 一起存进结构体 |
| cmp 参数写 Student x | 每次比较都复制整个结构体,数据大时明显变慢 | const Student &x |
| P5143 用 int 存平方和 | 平方后溢出 → 距离算错 | double dx = ...; 再 dx*dx |
| cout << ans 输出距离 | 小数位数不对,P5143 要求保留 3 位 | printf("%.3f\n", ans); |
struct Student {
string name;
int score;
}
答:末尾少了分号,应该是 };。
这是 C++ 里一个著名的坑:结构体/类的定义右花括号后面必须跟分号。
忘了它,编译器会以为你还要继续声明变量(比如 } a[1005]; 就是合法的),
等到后面遇到别的语句才报错,于是错误信息指向的位置往往离真正的错误有好几行。
bool cmp(const Student &x, const Student &y){
if(x.score != y.score) return x.score > y.score; // 分数不同:高的在前
return x.name < y.name; // 分数相同:名字小的在前
}
★ 关键是第一行用 != 判断「是否不同」,不同才由分数决定;
相同就自然落到第二行,由名字决定。
如果写成 if(x.score > y.score) return true;,那么分数相等时会掉到第二行,
于是「分数低但名字小」的人反而排在前面,全乱套了。
假设已经 sort(a, a+n, cmp) 按总分降序排好了(a[0] 是第一)。
答:第二名是 a[1],直接
cout << a[1].name << " " << a[1].total << endl;
★ 记住:数组下标从 0 开始,所以「第 k 名」是 a[k-1]。
P1093 输出前 5 名循环写的是 i = 1..5(输出 a[1]~a[5]),
那是因为它在 a[1] 开始存数据。下标基础不同,读代码时一定要先看清楚。
int cnt = 0;
for(int i = 0; i < n; i++){
if(a[i].total >= 200) cnt++;
}
cout << cnt << endl;
★ 这题不需要排序,只需要遍历一遍结构体数组。结构体题不一定要排序—— 先看清题目要「求最值 / 数个数」还是「排顺序」,再决定用不用 sort。
【结构体定义】
struct Student {
string name; // 成员用分号隔开
int chinese, math, english, total;
}; // ★ 这个分号必须有!
Student a[1005]; // 结构体数组
【访问成员】
a[i].name // 先锁定"第几个",再取"哪一项"
a[i].total = a[i].chinese + a[i].math + a[i].english; // 派生字段
【多关键字 cmp】
bool cmp(const Student &x, const Student &y){
if(x.total != y.total) return x.total > y.total; // 总分降序
if(x.chinese != y.chinese) return x.chinese > y.chinese; // 语文降序
return x.id < y.id; // 学号升序
}
sort(a + 1, a + n + 1, cmp); // ★ 左闭右开,右端点 +1
【cmp 心法】
cmp(x, y) 返回 true = x 排在 y 前面
想降序写 >,想升序写 <
并列条件用 if(... != ...) 筛,一层一层往下
【结构体题的固定套路】
读入 → 存 id/派生字段 → 写 cmp → sort → 输出前 k 个 / 输出最值
【三维距离】
double dx = p[i].x - p[i-1].x; // 先转 double 再平方,防溢出
ans += sqrt(dx*dx + dy*dy + dz*dz);
printf("%.3f\n", ans);
排序:sort 与它的朋友们
今天你已经摸到了 sort 和 cmp 的门,明天要把它们彻底讲透:
greater<int>() 降序、unique 去重、冒泡排序的原理,
以及 P1012「拼数」那个经典的拼接比较 a+b > b+a。
今天写的 cmp 明天还要用,而且会更难一点——记得回来复习这三行 if。