贪心问题-区间类
区间选点
问题描述
给定N个闭区间\([a_i,b_i]\),请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
输出选择的点的最小数量。
位于区间端点上的点也算作区间内。
问题分析
区间类的贪心问题解法大多都是按照区间端点进行排序,了解了规律之后,还需要清楚解法的证明
设最终答案为\(res\),按照图片中的规则得到的解为\(cnt\), 求证\(res == cnt\)
如果我们可以得到1.\(res <= cnt\) 和 2.\(res >= cnt\) 即可得到 \(res == cnt\)
由于res为最终答案,即最小数量,所以易知\(res <= cnt\)
暂定内容:按照y总所说,按照图片规则,没有被pass掉的区间构成的是下图这样的,下图一共是cnt个两两不相交的区间,想要覆盖这些区间,最少需要cnt个,所以满足res >= cnt,但是我并没有完全懂这个
代码实现
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, INF = 0x3f3f3f3f;
int n;
struct Range {
int l, r;
bool operator < (const Range &u) const {
return r < u.r;
}
} range[N];
int main()
{
cin >> n;
for (int i = 0; i < n; ++ i)
cin >> range[i].l >> range[i].r;
sort(range, range + n);
int res = 0, ed = -INF;
for (int i = 0; i < n; ++ i)
if (range[i].l > ed)
{
++ res;
ed = range[i].r;
}
cout << res << endl;
return 0;
}
最大不相交区间数量
问题描述
给定N个闭区间\([a_i,b_i]\),请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
输出可选取区间的最大数量。
问题分析
类似区间选点问题
代码实现
区间分组
问题描述
给定N个闭区间\([a_i,b_i]\),请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
问题分析
证明过程同样采用区间选点问题的证明方法,但是并没有想懂为什么需要按照左端点进行排序
需要保留当前所有组的右端点信息(这组中所有区间的最大右端点),对于当前区间,判断是否能够放入已有组中(能够放入某组的条件为当前组的右端点满足<当前区间左端点)
对于每个区间如果都枚举一遍所有组复杂度会变为\(O(n^2)\),所以采用小根堆存储所有组的右端点,如果某个区间的左端点<=min(所有组的右端点)(即小根堆堆顶),说明当前区间只能重新开一组;反之,将此区间放入堆顶的组。这里存在的疑问是:如果存在多组能够存放当前区间,为何将此区间放入右端点最小的组是最优解?因为当前区间是按照左端点从小到大进行排序的,当前区间的后续区间左端点一定大于当前区间左端点,所以此时能够容纳当前区间的组也一定能够容纳后续区间。也就是说当前区间放到哪个组中并不会对最终结果产生影响,也说不上什么最优解。
代码实现
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 10, INF = 0x3f3f3f3f;
int n;
struct Range {
int l, r;
bool operator < (const Range &u) const {
if (r == u.r) return l < u.l;
return l < u.l;
}
} range[N];
int main()
{
cin >> n;
for (int i = 0; i < n; ++ i) cin >> range[i].l >> range[i].r;
sort(range, range + n);
priority_queue<int, vector<int>, greater<int>> heap;
for (int i = 0; i < n; ++ i)
{
/**
* 堆中存储的为所有组的右端点
* 如果当前堆为空(还没有组)或者当前区间的左端点<=所有组中最小的右端点(无法将当前区间放入某一组中),则开一个新的组
* 如果能够放入一组中,则实际上是将堆顶表示的那一组的右端点数值更新为当前组的右端点,但是优先队列不支持随机修改,所以将堆顶需要修改的值弹出,放入修改后的值等价为修改了堆顶
*/
if (heap.empty() || range[i].l <= heap.top()) heap.push(range[i].r);
else
{
heap.pop();
heap.push(range[i].r);
}
}
cout << heap.size() << endl;
return 0;
}
区间覆盖
问题描述
给定\(N\)个闭区间\([a_i,b_i]\)以及一个线段区间\([s,t]\),请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出-1。
问题分析
证明过程同样不太理解
代码实现
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, INF = 0x3f3f3f3f;
struct Range {
int l, r;
bool operator< (const Range &u) const {
return l < u.l;
}
} range[N];
int n, st, ed;
int main()
{
cin >> st >> ed >> n;
for (int i = 0; i < n; ++ i) cin >> range[i].l >> range[i].r;
sort(range, range + n);
int res = 0;
for (int i = 0; i < n; ++ i)
{
int j = i, r = -INF;
while (j < n && range[j].l <= st)
{
r = max(r, range[j].r);
++ j;
}
if (r < st) // 找到的能够覆盖左端点的最大右端点值是小于左端点的,说明不存在能够覆盖左端点的区间,即无法完全覆盖
{
res = -1;
break;
}
++ res;
st = r; // 如果st = r + 1,那么[r, r+1]这段则无法被覆盖
if (st >= ed) break;
i = j - 1; // j并没有被选择过,考虑到i会++,所以这里等于j-1,下次刚好从未选择过的j开始
}
/**
* 结束循环可能有两种情况:
* 1. 满足st>=ed了 2.所有区间都遍历完了
* 对于下面这组数据,虽然没有出现非法情况,但是区间遍历完全后并没有覆盖全部区间,所以答案同样应该是-1,所以最后输出时应特判一下
* 1 3
* 1
* 0 1
*/
if (st < ed) res = -1; // 判断区间遍历完全但并未完全覆盖的情况
cout << res << endl;
return 0;
}