本文遵循 CC BY-NC-ND 4.0 协议,作者: U•ェ•*U \texttt{U•ェ•*U} U•ェ•*U,转载请获得作者授权。
欢迎大家来到进阶算法第一课:贪心;我会分为以下几点为大家讲解贪心:
- 什么是贪心。
- 贪心的性质与分类。
- 贪心 4 4 4 大模型。
- 贪心 3 3 3 种证明方法。
- 贪心的要点。
什么是贪心
贪心的定义:在问题决策的过程中,不考虑全局最优解,而只考虑局部最优解的算法。
但是,在部分题目中会得不到全局最优解,因此 OI \texttt{OI} OI 中的贪心多指 可以得到全局最优解的贪心算法 \color{red}\texttt{可以得到全局最优解的贪心算法} 可以得到全局最优解的贪心算法。
贪心的本质是利用题目性质的思维方法(有点类似于 骗分),而不是一个固定实现方法的算法,需要选手能够随机应变。
与动态规划的区别
贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。
贪心的性质
-
贪心选择性质:指问题的整体最优解可以通过一系列局部最优的选择来达到,而最优子结构则是在每一步贪心选择后,剩下的子问题仍然具有最优解的结构特性。这两个性质是贪心算法可行的基本要素,也是贪心算法与动态规划算法的主要区别。
-
最优子结构性质:当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
贪心的分类
- 从问题分类:
- 找最优
- 构造
- 反悔
- 交换
- 从证明方法分类:
- 反证法
- 归纳法
- 调整法(来自洛谷网校)
贪心相关算法模型
模型一:找最优
顾名思义,就是只需要找最优值(最大、最小等)的贪心模型,比较常见。
- 特点:通常出现在选物品的决策中,要求物品必须有一个属性可以比较(比如单价)。
- 但背包问题中每个物品的体积和价格都不一样,不能贪心。
- 方法:排序、优先队列等方法。
来道例题:P1208 混合牛奶。
相信大家已经会做这道题目了,我们来分析一下它:
显然,应该购买单价最低的牛奶,单价低的牛奶卖完了,再买次低价的。
先按单价从小到大排序,依次购买这个供应商的牛奶,直到买完或者买够为止。
完美 AC \texttt{AC} AC,其实,贪心还是比较简单易懂的。
C++ \texttt{C++} C++ 代码如下(风格比较奇怪):
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, ans;
struct node {
int p, a;
} s[1000010];
bool cmp(node a, node b) {
return a . p < b . p;
}
signed main() {
ios :: sync_with_stdio(false);
cin >> n >> m;
for (int i = 1; i <= m; i ++) {
cin >> s[i] . p >> s[i] . a;
}
sort(s + 1, s + m + 1, cmp);
for (int i = 1; i <= m; i ++) {
if (n <= 0) break;
if (s[i] . a >= n) {
ans += s[i] . p * n;
n = 0;
} else ans += s[i] . p * s[i] . a, n -= s[i] . a;
}
cout << ans << endl;
return 0;
}
再来道例题:P1803 线段覆盖。
也很简单,就不给代码了,讲讲思路:
我们只需要按照右端点递增的顺序排序,然后顺序枚举,能选就选,不能就算了。
但是为什么这么做是对的呢?我们来一起证明一下:
对于排序后的第 i i i 个线段,以及后面的第 j j j 个
线段,假设 i , j i,j i,j 当前都可以选择,不选 i i i 而选 j j j 不可能更优。
证明还是很简单的,但这仅限于这种一眼看懂的题目。
模型二:构造
构造相信大家再熟悉不过了,就是直接生成一个符合要求的方案即可。
- 特点:构造出符合题目要求的方案。
- 方法:直接构造一种最优决策,然后判断这种方案是不是满足要求,满足就直接返回结果。
例题:ABC167F
其实,有一个很经典的括号匹配:用一个栈,遇到左括号就压进去,遇到右括号就把栈里的左括号弹出来,形成匹配。
但是,题目要求的是输出最后的匹配顺序,又该怎么做呢?
我们可以贪心的考虑这道题目,先将字符串全部排序,尽量把左括号放左边,右括号放右边,再进行匹配和计数。
证明也很简单:因为我们把左括号尽量放左边了,那么除非是无解的情况,左右括号都是能够一一配对的。
上 C++ \texttt{C++} C++ 代码(但是不知道哪里有问题,只能得 50 pts \texttt{50 pts} 50 pts,欢迎在私信我指出问题,感谢 ~):
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e6 + 10;
int n;
string s[MAXN];
struct node {
int x, y, z, id;
} b[MAXN];
bool cmp(const node &a, const node &b) {
if (a . z != b . z) return a . z < b . z;
if (a . y != b . y) return a . y < b . y;
return a . id < b . id;
}
void chuli(int x) {
int cnt1 = 0, cnt2 = 0;
for (int i = 0; i < s[x] . length(); i ++) {
if (s[x][i] == '(') cnt1 ++;
else {
if (cnt1 > 0) cnt1 --;
else cnt2 ++;
}
}
b[x] = (node){
cnt1, cnt2, 0, x};
if (cnt1 != 0 && cnt2 != 0) b[x] . z = 1;
else if (cnt1 == 0 && cnt2 != 0) b[x] . z = 4;
else if (cnt1 == 0 && cnt2 == 0) b[x] . z = 0;
else if (cnt1 >= cnt2) b[x] . z = 2;
else if (cnt2 > cnt1) b[x] . z = 3;
}
signed main() {
ios :: sync_with_stdio(false);
cin . tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> s[i];
for (int i = 1; i <= n; i ++) chuli(i);
sort(b + 1, b + n + 1, cmp);
int cnt = 0;
for (int i = 1; i <= n; i ++) {
if (b[i] . z == 0) continue;
else if (b[i] . z == 1) cnt += b[i] . x;
else {
if (cnt < b[i] . y) {
cnt = -1;
break;
}
cnt -= b[i] . y;
cnt += b[i] . x;
}
}
if (cnt > 0 || cnt == -1) cout << -1 << endl;
else {
for (int i = 1; i <= n; i ++) {
cout << b[i] . id << " ";
}
cout << endl;
}
return 0;
}
模型三:反悔
又称反悔贪心:
- 特点:方案具有后效性。
- 方法:不立刻决策,等待决策发生影响时再进行。
2023 2023 2023 年 CSP-J \texttt{CSP-J} CSP-J 就有反悔贪心题,来一起看看:P9749 [CSP-J 2023] 公路
经典、简单的返回贪心思想题。
我们从左到右考虑,如果行驶到某个加油站,这时刚好缺油了,就从之前经过的最便宜的加油站加油。
我们考虑维护变量

最低0.47元/天 解锁文章
2199

被折叠的 条评论
为什么被折叠?



