#9989. 2026/8/3/WWX笔记(贪心)
2026/8/3/WWX笔记(贪心)
贪心算法课堂笔记
一、今天学习了什么
今天的题目主要使用了“排序+贪心”。
贪心的做法是:
每一步都选择当前最合适的方案。
今天练习的主要题型:
- 用最少的人或物品达到目标;
- 在容量内尽量多拿物品;
- 每组最多放两个物品;
- 两组数据进行最优匹配;
- 数字和字符串的贪心排列;
- 删除数字后得到最大数或最小数;
- 按单价排序后购买物品。
二、先判断排序方向
贪心题经常要先排序。最重要的是判断应该升序还是降序。
| 题目目标 | 常用排序方向 |
|---|---|
| 用最少数量达到目标 | 从大到小 |
| 容量一定,尽量多拿物品 | 从小到大 |
| 花钱尽量少 | 单价从小到大 |
| 每组最多两个,组数最少 | 从小到大,再用双指针 |
| 两组数据差值尽量小 | 两组都从小到大 |
| 拼接得到最大数 | 使用专门的字符串比较函数 |
升序:
sort(a,a+n);
降序:
sort(a,a+n,greater<int>());
做题前先问自己:
我应该优先选择大的,还是优先选择小的?
三、模型1:用最少数量达到目标
思考方法
希望选择的数量最少,就应该优先选择贡献最大的元素。
做法:
- 从大到小排序;
- 从前往后累加;
- 第一次达到目标时停止。
通用模板
sort(a,a+n,greater<int>());
int sum=0,ans=0;
for(int i=0;i<n;i++){
sum+=a[i];
ans++;
if(sum>=m) break;
}
对应题目
6377 赶工的Aki:选择最少的师傅完成零件;4794 当总统:选择最少的州,并取得每个州刚好过半的选票。
赶工的Aki易错点
输入顺序是:
需要加工的零件数m
师傅人数n
如果所有师傅的加工量之和仍然不够,要输出:
NO
不能输出 -1。
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int m,n,a[N];
int main(){
cin>>m>>n;
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n,greater<int>());
int sum=0;
for(int i=0;i<n;i++){
sum+=a[i];
if(sum>=m){
cout<<i+1;
return 0;
}
}
cout<<"NO";
return 0;
}
四、模型2:容量有限,尽量多拿
思考方法
每个物品都只计算“一个”,想让数量最多,就应该先拿最轻的。
做法:
- 从小到大排序;
- 依次尝试放入;
- 放不下时停止。
sort(a,a+n);
int sum=0,cnt=0;
for(int i=0;i<n;i++){
if(sum+a[i]<=x){
sum+=a[i];
cnt++;
}else break;
}
对应题目:1932 淘淘捡西瓜。
对比记忆:
数量最少达到目标:先选大的。
容量有限数量最多:先选小的。
五、模型3:最轻和最重配对
独木舟问题
每条独木舟最多坐两个人,总重量不能超过 w,求最少独木舟数量。
先排序,再使用两个指针:
int l=0,r=n-1;
l指向最轻的人;r指向最重的人。
每次先安排最重的人:
- 如果最轻的人能和他同坐,就让两人一起坐;
- 如果最轻的人都不能和他同坐,最重的人只能单独坐。
#include<bits/stdc++.h>
using namespace std;
const int N=30010;
int w,n,a[N];
int main(){
cin>>w>>n;
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n);
int l=0,r=n-1,ans=0;
while(l<=r){
if(l<r&&a[l]+a[r]<=w) l++;
r--;//最重的人已经被安排
ans++;
}
cout<<ans;
return 0;
}
易错点
这道题不是“两数之和”。
题目没有要求找到两个人的体重和恰好等于 w,而是要求:
每条船不超重,并且船的数量最少。
所以判断条件是:
a[l]+a[r]<=w
不是:
a[l]+a[r]==w
六、模型4:刚好能赢的匹配
田忌赛马
把双方的马都从小到大排序。
sort(a,a+n);
sort(b,b+n);
用自己当前最慢的马 a[l],尝试战胜对方当前最慢的马 b[r]:
- 如果
a[l]>b[r],完成一次获胜匹配; - 如果
a[l]<b[r],自己的这匹马连对方最慢的马都赢不了,只能放弃。
#include<bits/stdc++.h>
using namespace std;
const int N=50010;
int n,a[N],b[N];
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++) cin>>b[i];
sort(a,a+n);
sort(b,b+n);
int l=0,r=0,ans=0;
while(l<n&&r<n){
if(a[l]>b[r]){
ans++;
l++,r++;
}else l++;
}
cout<<ans;
return 0;
}
输入易错点
这道题只有一个人数 n,双方都是 n 匹马。
不能写成:
cin>>n>>m;
正确写法:
cin>>n;
两组排序后一一匹配
如果要让两组数据的差值总和最小,就把两组都从小到大排序,再让相同位置配对。
sort(a,a+n);
sort(b,b+n);
for(int i=0;i<n;i++){
ans+=abs(a[i]-b[i]);
}
对应题目:2058 滑翔翼。
七、模型5:分组贪心
1. 书架
每层最多放 m 本书,每层高度由最高的书决定。
把书从高到低排序,每 m 本放一层。每组第一本书最高,需要计入总高度。
sort(a,a+n,greater<int>());
int sum=0;
for(int i=0;i<n;i+=m){
sum+=a[i];
}
对应题目:4797 书架。
2. 购书
每3本书中最便宜的一本免费。
为了让免费金额尽量大,应把价格从高到低排序,每连续3本分成一组。
sort(a,a+n,greater<int>());
long long ans=0;
for(int i=0;i<n;i++){
if(i%3!=2) ans+=a[i];//每组第3本免费
}
这种写法不会访问数组范围外的位置,比依赖全局数组中的 0 更清楚、更安全。
对应题目:4799 购书。
3. 当总统
要赢得超过一半的州,先选择“拿下所需选票最少”的州。
一个州有 a[i] 名选民,拿下该州至少需要:
a[i]/2+1
先把州人数从小到大排序,再选择前:
m/2+1
个州。
八、模型6:按价格从低到高购买
1. 买饮料最省钱
每家店有单价和库存。要买恰好 m 瓶并使花费最少,就先去单价最低的店。
结构体:
struct fd{
long long a,b;//a是单价,b是库存
};
比较函数:
bool f(fd s1,fd s2){
return s1.a<s2.a;
}
每家店购买:
long long x=min(s[i].b,m);
ans+=x*s[i].a;
m-=x;
完整模板:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct fd{
long long a,b;
};
fd s[N];
int n;
long long m,ans;
bool f(fd s1,fd s2){
return s1.a<s2.a;
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++) cin>>s[i].a>>s[i].b;
sort(s,s+n,f);
for(int i=0;i<n&&m>0;i++){
long long x=min(s[i].b,m);
ans+=x*s[i].a;
m-=x;
}
cout<<ans;
return 0;
}
2. 圣诞礼物
预算一定,希望买到的礼物数量最多,也应该先买单价低的礼物。
区别是:
饮料题:数量固定,求最少花费。
礼物题:预算固定,求最多数量。
两题都要按单价升序排序。
九、数字和字符串排序
1. 数字变位
组成最大数:数字从大到小排序。
组成最小数:数字从小到大排序,但最高位不能是 0。
sort(s.begin(),s.end());
for(int i=0;i<s.size();i++){
if(s[i]!='0'){
swap(s[0],s[i]);
break;
}
}
对应题目:1102 数字变位。
2. 拼接最大数
不能直接比较 a>b,因为需要判断谁放在前面能让拼接结果更大。
bool f(string a,string b){
return a+b>b+a;
}
例如:
a="12",b="121"
a+b="12121"
b+a="12112"
所以 12 应该放在 121 前面。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
string s[N];
int n;
bool f(string a,string b){
return a+b>b+a;
}
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>s[i];
sort(s,s+n,f);
if(s[0]=="0"){
cout<<0;
return 0;
}
for(int i=0;i<n;i++) cout<<s[i];
return 0;
}
如果排序后第一项是 "0",说明所有数字都是 0,直接输出一个 0。
十、删数字贪心
删数字时,剩余数字的相对顺序不能改变。
1. 删除后得到最大数
从左向右找到第一处:
s[i]<s[i+1]
删除较小的 s[i],可以让更大的数字提前。
69134
6<9,先删除6
如果整个字符串从大到小排列,就删除最后一位。
对应题目:
4917 删除数字6477 删除数字
2. 删除后得到最小数
从左向右找到第一处:
s[i]>s[i+1]
删除较大的 s[i],可以让更小的数字提前。
如果整个字符串从小到大排列,就删除最后一位。
对应题目:1839 删数问题。
3. 两种方向不要混淆
求最大:前面小、后面大,删除前面的小。
求最小:前面大、后面小,删除前面的大。
4. 循环边界
程序会访问 s[i+1],所以循环必须写:
for(int i=0;i<s.size()-1;i++)
不能写:
for(int i=0;i<s.size();i++)
否则最后一次会访问字符串外面。
5. 删除最小数的完整模板
#include<bits/stdc++.h>
using namespace std;
string s;
int k;
int main(){
cin>>s>>k;
while(k--){
bool ok=0;
for(int i=0;i<s.size()-1;i++){
if(s[i]>s[i+1]){
s.erase(i,1);
ok=1;
break;
}
}
if(!ok) s.erase(s.size()-1,1);
}
while(s.size()>1&&s[0]=='0') s.erase(0,1);
cout<<s;
return 0;
}
判断前导零时,要先判断 s.size()>1,再访问 s[0]。
十一、重点订正:1091 最小字典序
题目特点
必须交换一次两个位置,使新字符串字典序最小。
为了让字符串尽量小,先寻找最靠左的、能够被更小字符替换的位置。
对于这个位置:
- 选择右边最小的字符;
- 如果最小字符出现多次,选择最靠右的一个进行交换。
90分代码的两个问题
问题1:记录字符位置的数组没有初始化。
局部数组:
int a[26];
里面的初始值不确定,必须先设为 -1:
for(int i=0;i<26;i++) a[i]=-1;
问题2:如果无法让字符串变小,不能总是交换前两个字符。
- 如果有相同字符,交换两个相同字符,字符串不变,这是最小的;
- 如果所有字符都不同并且已经升序,交换最后两个字符影响最靠后,结果最小。
正确代码
#include<bits/stdc++.h>
using namespace std;
string s;
int last[26];
int main(){
cin>>s;
for(int i=0;i<26;i++) last[i]=-1;
for(int i=0;i<s.size();i++){
last[s[i]-'a']=i;//记录每个字符最后出现的位置
}
for(int i=0;i<s.size();i++){
for(char c='a';c<s[i];c++){
if(last[c-'a']>i){
swap(s[i],s[last[c-'a']]);
cout<<s;
return 0;
}
}
}
//无法变小时,优先交换两个相同字符
for(int i=1;i<s.size();i++){
if(s[i]==s[i-1]){
cout<<s;
return 0;
}
}
//字符严格递增,交换最后两个字符
swap(s[s.size()-2],s[s.size()-1]);
cout<<s;
return 0;
}
十二、今日题目与模型对应
| 题号 | 题目 | 使用的模型 |
|---|---|---|
| 4339 | 田忌赛马 | 排序后双指针匹配 |
| 3848 | 独木舟 | 最轻和最重配对 |
| 4797 | 书架 | 降序分组 |
| 4799 | 购书 | 每3本一组 |
| 1102 | 数字变位 | 数字字符排序、处理前导零 |
| 6787 | 拼接最大数 | 字符串拼接比较 |
| 4917 | 删除数字 | 删除后求最大数 |
| 6477 | ||
| 1839 | 删数问题 | 删除后求最小数 |
| 6377 | 赶工的Aki | 从大到小凑目标 |
| 1932 | 淘淘捡西瓜 | 从小到大尽量多拿 |
| 6298 | 买饮料最省钱 | 按单价升序购买 |
| 4610 | 圣诞礼物 | 预算内先买便宜的 |
| 2058 | 滑翔翼 | 两组排序后一一匹配 |
| 1091 | 最小字典序 | 一次交换、最后位置 |
| 4794 | 当总统 | 选择代价最小的州 |
十三、考前快速复习
1. 看到题目先问
要求数量最少,还是数量最多?
应该先选大的,还是先选小的?
是否需要排序?
是否可以使用左右指针?
2. 双指针检查
l和r分别表示什么?
每一轮至少有一个指针移动吗?
循环条件是l<r还是l<=r?
3. 字符串检查
求最大还是求最小?
访问s[i+1]时,i有没有小于s.size()-1?
删除后要不要清除前导零?
4. 结构体排序检查
比较的是单价、数量还是总价?
升序和降序有没有写反?
乘法和答案是否需要long long?
最重要的四句话:
用最少数量达到目标,通常先选大的。
容量有限想多拿,通常先选小的。
每组最多两个,通常考虑最轻和最重配对。
删数字求最大和求最小,比较符号正好相反。