本文为算法学习笔记,讲解贪心。欢迎交流
简单贪心
贪心法总是考虑当前状态的局部最优,从而全局结果达到最优。贪心法的证明使用反证法证明策略最优,然后用数学归纳法保证全局最优。因为贪心法证明较为困难,如果在想到可行策略,自己却无法举出反例时,可大胆尝试。
下面是一个简单的例子:
区间贪心
区间不相交问题:给出 N 个开区间 (x,y),选择尽可能多的开区间,使得开区间两两没有交集。
如果开区间 I 1 I_1 I1 被开区间 I 2 I_2 I2 包含,则应选择 I 1 I_1 I1,这样能有更大的空间容纳其他开区间。
将所有开区间按左端点 x 从大到小排序(如下图),如果除去区间包含的情况,则有 y 1 > . . . > y n y1>...>y_n y1>...>yn 成立。
通过观察发现, I 1 I_1 I1 右边有一段一定不会与其他区间重叠,如果将其去掉,则 I 1 I_1 I1 左边剩余部分会被 I 2 I_2 I2 包含。因此应该选择 I 1 I_1 I1。所以我们总是选择左端点最大的区间。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 110;
vector<pair<int, int>> I; // pair表示开区间左右端点
bool cmp(pair<int, int> a, pair<int, int> b) {
if (a.first != b.first) return a.first > b.first; // 先按左端点从大到小排序
else return a.second < b.second;
}
int main() {
int n;
while (scanf("%d", &n), n != 0) {
I.clear(); I.resize(n);
for (int i = 0; i < n; ++i)
scanf("%d%d", &I[i].first, &I[i].second);
sort(I.begin(), I.end(), cmp); // 区间排序
// ans记录不相交区间的个数,lastX记录上一个被选中区间的左端点
int ans = 1, lastX = I[0].first;
for (int i = 1; i < n; ++i)
if (I[i].second <= lastX) { // 如果该区间右端点在lastX左边
lastX = I[i].first; // 以I[x]作为选中的新区间
++ans;
}
printf("%d\n", ans);
}
return 0;
}
同理也可以先选择右端点最小的区间。
类似的问题,区间选点问题:给出 N 个闭区间 [x,y],求至少需要确定多少个点,才能使每个闭区间中都至少存在一个点。对于前面图中的 I 1 I_1 I1,取左端点可以让它尽可能多地覆盖其他区间。
因为当闭区间相接时只需要左端点,将上面的代码中 I[i].second <= lastX
改为 I[i].second < lastX
即可。
贪心算法满足最优子结构性质,即一个问题的最优解可以由其子问题的最优解有效构造出来。