区间问题
贪心算法解决区间问题三步走:
- 创建Range结构体;
- 按照左端点或右端点对区间进行排序;
- 利用贪心算法求解。
一、区间选点
给定 N 个闭区间 [ ai , bi ],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。输出选择的点的最小数量。位于区间端点上的点也算作区间内。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int n;
//步骤1
struct Range{
int l,r;
bool operator<(const Range &W)const{
return r<W.r;//按照右端点排序。
}
}range[N];
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>range[i].l>>range[i].r;
//步骤2
sort(range ,range+n );
//步骤3
int res=0,ed=-2e9;//第一个区间有一个点
for(int i=0;i<n;i++)
if(ed<range[i].l){//判断排序后的相邻区间是否相交
res++;//相交则点的数量加一
ed=range[i].r;
}
cout<<res;
return 0;
}
求“最大不相交区间个数”的思路和代码与本题一样。
二、区间分组
给定 N 个闭区间 [ ai , bi ],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。输出最小组数。
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=100010;
//步骤1
struct Range{
int l,r;
bool operator < (const Range &W){
return l < W.l;//按照左端点排序。
}
}range[N];
int n;
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>range[i].l>>range[i].r;
//步骤2
sort(range,range+n);
//步骤3
priority_queue<int,vector<int>,greater<int>> heap;//小根堆
for(int i=0;i<n;i++){
if(heap.empty()||heap.top()>=range[i].l) heap.push(range[i].r);
else{
heap.pop();
heap.push(range[i].r);
}
}
cout<<heap.size()<<endl;
return 0;
}
构建小根堆后,我们需要比较,已经分组的区间的最小右端点和此次循环中区间的左端点的大小:
- 若最小右端点更大,则需要一个新的分组。
- 若当前左端点更大,则将该区间加入到任意一个分组中去。
对于每个未分组的区间,我们只考查它的左端点,因此按照左端点排序。
三、区间覆盖
给定 N 个闭区间以及一个线段区间 [ s , t ],请你选择尽量少的区间,将指定线段区间完全覆盖。输出最少区间数,如果无法完全覆盖则输出 −1。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
//步骤1
struct Range{
int l,r;
bool operator < (const Range &W){
return l < W.l;
}
}range[N];
int s,t,n;
int main(){
cin>>s>>t>>n;
for(int i=0;i<n;i++) cin>>range[i].l>>range[i].r;
//步骤2
sort(range,range+n);
//步骤3
int res=0;
bool success=false;
for(int i=0;i<n;i++){
int j=i,r=-2e9;
while(j<n&&range[j].l<=s){
r=max(r,range[j].r);
j++;
}
if(r<s){
res=-1;
break;
}
res++;
s=r;
i=j-1;
if (r>=t){
success =true;
break;
}
}
if (!success) res=-1;
cout<<res<<endl;
return 0;
}
不可以覆盖的情况有三种:开头空缺,中间空缺和结尾空缺。
开头空缺时循环不必全部进行,结尾空缺时循环依然会正常结束。
中间空缺通过更新线段左端点转换为开头空缺。我们需要比较,未被覆盖的线段的左端点和此次循环中区间的左端点的大小:
- 若线段左端点更大,则被覆盖,更新未被覆盖的线段左端点。
- 若当前左端点更大,则未被覆盖,有开头空缺。
只有可以线段完全被覆盖的时候才输出对应的 res,否则输出 -1。
对于当前循环的区间,我们只考查它的左端点,因此按照左端点排序。