🚀 C++ STL神器 | sort排序实战!
导语 :
在算法竞赛和日常编程中,排序绝对是出场率最高的操作之一。如果你还在手写几十行的冒泡排序,那你可就亏大了!今天带大家通过三道经典的洛谷真题,彻底掌握 C++ STL 库中的超级神器------
sort函数。学会它,一行代码就能搞定复杂的排序任务!💪
01 📖 sort 的基本用法与贪心策略
sort 函数包含在 <algorithm> 头文件中。默认情况下,它会按照升序(从小到大)排列元素。但在很多实际问题中,我们需要让它从大到小 排,这时候就需要用到自定义比较函数 cmp。
🐄 P2676 USACO07DEC Bookshelf B
题目描述
Farmer John 最近为奶牛们的图书馆添置了一个巨大的书架,尽管它是如此的大,但它还是几乎瞬间就被各种各样的书塞满了。现在,只有书架的顶上还留有一点空间。
所有 N(1≤N≤20,000)N(1 \le N \le 20,000)N(1≤N≤20,000) 头奶牛都有一个确定的身高 Hi(1≤Hi≤10,000)H_i(1 \le H_i \le 10,000)Hi(1≤Hi≤10,000)。设所有奶牛身高的和为 SSS。书架的高度为 BBB,并且保证 1≤B≤S<2,000,000,0071 \le B \le S < 2,000,000,0071≤B≤S<2,000,000,007。
为了够到比最高的那头奶牛还要高的书架顶,奶牛们不得不像演杂技一般,一头站在另一头的背上,叠成一座"奶牛塔"。当然,这个塔的高度,就是塔中所有奶牛的身高之和。为了往书架顶上放东西,所有奶牛的身高和必须不小于书架的高度。
显然,塔中的奶牛数目越多,整座塔就越不稳定,于是奶牛们希望在能够到书架顶的前提下,让塔中奶牛的数目尽量少。现在,奶牛们找到了你,希望你帮她们计算这个最小的数目。
输入格式
- 第 111 行:2 个用空格隔开的整数:NNN 和 BBB;
- 第 2...N+12\dots N+12...N+1 行:第 i+1i+1i+1 行是 111 个整数:HiH_iHi。
输出格式
- 第 111 行:输出 111 个整数,即最少要多少头奶牛叠成塔,才能够到书架顶部。
输入输出样例 #1
输入 #1
text
6 40
6
18
11
13
19
11
输出 #1
text
3
说明/提示
输入说明:
一共有 666 头奶牛,书架的高度为 404040,奶牛们的身高在 6...196\dots196...19 之间。
输出说明:
一种只用 333 头奶牛就达到高度 404040 的方法:18+11+1318+11+1318+11+13。当然还有其他方法,在此不一一列出了。
💡 思路解析
这是一道典型的贪心 题目。为了让奶牛数量最少,我们肯定优先挑选身高最高的奶牛!
因此,解题步骤非常简单:
- 将所有奶牛的身高存入数组;
- 使用
sort配合自定义cmp函数,将身高从大到小排序; - 从高到低依次累加身高,直到总高度大于等于书架高度 BBB 为止。
💻 代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
// 自定义比较函数:让身高高的排在前面 (降序)
bool cmp(long long a, long long b){
return a > b;
}
int main(){
long long n, b;
cin >> n >> b;
vector<long long> v(n);
for(long long i = 0; i < n; i++){
cin >> v[i];
}
// 调用 sort 进行降序排序
sort(v.begin(), v.end(), cmp);
long long ans = 0;
long long sum = 0;
long long i = 0;
// 贪心累加,直到够到书架顶
while(sum < b){
sum += v[i++];
ans++;
}
cout << ans << endl;
return 0;
}
02 🏗️ 进阶玩法:结构体多关键字排序
在实际做题时,我们经常遇到这样的需求:有一堆学生,先按成绩从高到低排;如果成绩相同,再按其他条件排。这时候,结构体排序就派上用场了!
🎓 P1093 NOIP 2007 普及组 奖学金
题目背景
NOIP2007 普及组 T1
题目描述
某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前 555 名学生发奖学金。期末,每个学生都有 333 门课的成绩:语文、数学、英语。先按总分从高到低排序,如果两个同学总分相同,再按语文成绩从高到低排序,如果两个同学总分和语文成绩都相同,那么规定学号小的同学排在前面,这样,每个学生的排序是唯一确定的。
任务:先根据输入的 333 门课的成绩计算总分,然后按上述规则排序,最后按排名顺序输出前五名名学生的学号和总分。
注意,在前 555 名同学中,每个人的奖学金都不相同,因此,你必须严格按上述规则排序。例如,在某个正确答案中,如果前两行的输出数据(每行输出两个数:学号、总分) 是:
plain
7 279
5 279
这两行数据的含义是:总分最高的两个同学的学号依次是 777 号、555 号。这两名同学的总分都是 279279279 (总分等于输入的语文、数学、英语三科成绩之和) ,但学号为 777 的学生语文成绩更高一些。
如果你的前两名的输出数据是:
plain
5 279
7 279
则按输出错误处理,不能得分。
输入格式
共 n+1n+1n+1 行。
第 111 行为一个正整数 5≤n≤3005\le n \le 3005≤n≤300,表示该校参加评选的学生人数。
第 222 到 n+1n+1n+1 行,每行有 333 个用空格隔开的数字,每个数字都在 000 到 100100100 之间。第 jjj 行的 333 个数字依次表示学号为 j−1j-1j−1 的学生的语文、数学、英语的成绩。每个学生的学号按照输入顺序编号为 1∼n1\sim n1∼n(恰好是输入数据的行号减 111)。
保证所给的数据都是正确的,不必检验。
输出格式
共 555 行,每行是两个用空格隔开的正整数,依次表示前 555 名学生的学号和总分。
输入输出样例 #1
输入 #1
text
6
90 67 80
87 66 91
78 89 91
88 99 77
67 89 64
78 89 98
输出 #1
text
6 265
4 264
3 258
2 244
1 237
输入输出样例 #2
输入 #2
text
8
80 89 89
88 98 78
90 67 80
87 66 91
78 89 91
88 99 77
67 89 64
78 89 98
输出 #2
text
8 265
2 264
6 264
1 258
5 258
💡 思路解析
这道题的核心考点就是结构体排序 。我们需要定义一个包含学生信息的结构体,然后根据题目的三条规则编写自定义的比较函数 cmp。
我们可以用 if-else 层层判断,也可以利用逻辑运算符把这些条件串联起来。
💻 代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
struct student{
int chinese;
int math;
int english;
int score;
int id;
};
// 核心比较规则
bool cmp(const student& a, const student& b){
if(a.score != b.score) return a.score > b.score; // 1. 总分降序
else if(a.chinese != b.chinese) return a.chinese > b.chinese; // 2. 语文降序
else return a.id < b.id; // 3. 学号升序
}
int main(){
int n;
cin >> n;
vector<student> v;
int i = 1;
while(n--){
student temp;
cin >> temp.chinese >> temp.math >> temp.english;
temp.score = temp.chinese + temp.math + temp.english;
temp.id = i++;
v.push_back(temp);
}
// 使用 stable_sort 或 sort 均可,因为 cmp 已经定义了全序关系
stable_sort(v.begin(), v.end(), cmp);
// 输出前 5 名
for(int i = 0; i < 5; ++i){
cout << v[i].id << " " << v[i].score << endl;
}
return 0;
}
03 📝 综合应用:排序与业务逻辑处理
学会了排序只是第一步,如何将排序后的数据应用到实际的筛选逻辑中,才是拿分的关键。
🎟️ P1068 NOIP 2009 普及组 分数线划定
题目描述
世博会志愿者的选拔工作正在 A 市如火如荼的进行。为了选拔最合适的人才,A 市对所有报名的选手进行了笔试,笔试分数达到面试分数线的选手方可进入面试。面试分数线根据计划录取人数的 150%150\%150% 划定,即如果计划录取 mmm 名志愿者,则面试分数线为排名第 m×150%m \times 150\%m×150%(向下取整)名的选手的分数,而最终进入面试的选手为笔试成绩不低于面试分数线的所有选手。
现在就请你编写程序划定面试分数线,并输出所有进入面试的选手的报名号和笔试成绩。
输入格式
第一行,两个整数 n,m(5≤n≤5000,3≤m≤n)n,m(5 \leq n \leq 5000,3 \leq m \leq n)n,m(5≤n≤5000,3≤m≤n),中间用一个空格隔开,其中 nnn 表示报名参加笔试的选手总数,mmm 表示计划录取的志愿者人数。输入数据保证 m×150%m \times 150\%m×150% 向下取整后小于等于 nnn。
第二行到第 n+1n+1n+1 行,每行包括两个整数,中间用一个空格隔开,分别是选手的报名号 k(1000≤k≤9999)k(1000 \leq k \leq 9999)k(1000≤k≤9999)和该选手的笔试成绩 s(1≤s≤100)s(1 \leq s \leq 100)s(1≤s≤100)。数据保证选手的报名号各不相同。
输出格式
第一行,有 222 个整数,用一个空格隔开,第一个整数表示面试分数线;第二个整数为进入面试的选手的实际人数。
从第二行开始,每行包含 222 个整数,中间用一个空格隔开,分别表示进入面试的选手的报名号和笔试成绩,按照笔试成绩从高到低输出,如果成绩相同,则按报名号由小到大的顺序输出。
输入输出样例 #1
输入 #1
text
6 3
1000 90
3239 88
2390 95
7231 84
1005 95
1001 88
输出 #1
text
88 5
1005 95
2390 95
1000 90
1001 88
3239 88
说明/提示
【样例说明】
m×150%=3×150%=4.5m \times 150\% = 3 \times150\% = 4.5m×150%=3×150%=4.5,向下取整后为 444。保证 444 个人进入面试的分数线为 888888,但因为 888888 有重分,所以所有成绩大于等于 888888 的选手都可以进入面试,故最终有 555 个人进入面试。
NOIP 2009 普及组 第二题
💡 思路解析
这道题结合了结构体排序和实际的业务逻辑:
- 计算划线排名 :k=m×1.5k = m \times 1.5k=m×1.5(向下取整)。
- 确定分数线 :排序后第 kkk 个人的分数即为面试分数线
line。 - 处理同分扩招 :虽然理论上线内只有 kkk 个人,但因为可能有重分,所以只要分数 ≥\ge≥
line的人都能进面试。我们需要遍历一遍统计实际人数。
💻 代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
struct people{
int id;
int score;
};
// 比较规则:分数降序,分数相同则报名号升序
bool cmp(const people& a, const people& b){
if(a.score != b.score) return a.score > b.score;
else return a.id < b.id;
}
int main(){
int n, m;
cin >> n >> m;
int k = (int)(m * 1.5); // 计算理论上的录取人数
vector<people> v;
for(int i = 0; i < n; i++){
people p;
cin >> p.id >> p.score;
v.push_back(p);
}
sort(v.begin(), v.end(), cmp);
// 确定面试分数线(注意下标是 k-1)
int line = v[k - 1].score;
// 统计实际进入面试的人数(处理同分情况)
int count = 0;
for(int i = 0; i < n; i++){
if(v[i].score >= line) count++;
else break;
}
cout << line << " " << count << endl;
// 输出所有进入面试的选手
for(int i = 0; i < count; i++){
cout << v[i].id << " " << v[i].score << endl;
}
return 0;
}
⚠️ 避坑指南(重点!)
在使用 sort 时,有几个新手极易踩的"坑",大家一定要拿小本本记下来:
- 比较函数的返回值 :在写自定义
cmp函数时,返回值必须是true或false。千万不要写成return a - b;这种形式放在bool函数里,容易导致未定义行为。老老实实写return a > b;最稳妥! - 边界问题 :在《分数线划定》中,求第 kkk 名的分数时,数组下标应该是
v[k-1],因为数组是从 0 开始计数的。 - 稳定性 :虽然《奖学金》这道题用了
stable_sort(稳定排序),但其实只要你的cmp函数把所有可能的情况(总分、语文、学号)都判断清楚了,普通的sort也是完全没问题的。
(觉得有用的话,别忘了点赞、在看、转发三连哦!) ❤️