区间问题
区间问题是贪心问题的一大类,处理区间问题通常可以从排序入手。可以按照左端点排序,右端点排序或者双关键字排序。
区间选点
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 10;
int n,ans;
struct Range{
int l,r;
}range[maxn];
bool cmp(Range a,Range b){
return a.r < b.r;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int a,b;
scanf("%d%d",&a,&b);
range[i] = {a,b};
}
sort(range+1,range+1+n,cmp);
int ed = 0x3f3f3f3f * -1;
for(int i=1;i<=n;i++){
if(range[i].l > ed){
ans ++;
ed = range[i].r;
}
}
cout<<ans;
return 0;
}
思路:首先将所有的区间按照右端点从小到大排序,如果当前区间与上一个区间的没有交集。在最终的结果集合,增加选择当前区间的右端点。如果有交集,直接pass.
利用贪心的思想,每次考虑当前的最优值而当前的最优值又恰好是全局的最优值。
最大不相交区间数量
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 10;
int n,ans;
struct Range{
int l,r;
}range[maxn];
bool cmp(Range a,Range b){
return a.r < b.r;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int a,b;
scanf("%d%d",&a,&b);
range[i] = {a,b};
}
sort(range + 1,range + 1 + n,cmp);
int ed = -1 * 0x3f3f3f3f;
for(int i = 1;i<=n;i++){
if(range[i].l > ed){
ans ++;
ed = range[i].r;
}
}
cout<<ans;
return 0;
}
思路和上一题一样吗,这里给出贪心策略的证明。记答案为ans,贪心策略所选出的解是cnt。为了证明贪心值等于最优值。需要证明ans=cnt。等价地,需要证明ans>=cnt,ans<=cnt。
根据题干,容易证明ans>=cnt。
接着证明ans<=cnt.以区间两两无交为例,采用反证法,假设ans>cnt。由于区间是两两无交的,根据贪心策略,每个区间都会包含一个点而ans>cnt可得至少有一个区间中包含了两个点。相当于这个区间被选择了两次,这与题干矛盾,假设不成立。其余情况类似,可证名ans<=cnt。
综上,ans=cnt.
区间分组
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 10;
int n;
struct Range{
int l,r;
}range[maxn];
bool cmp(Range a,Range b){
return a.l < b.l;
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
scanf("%d%d",&range[i].l,&range[i].r);
}
sort(range,range+n,cmp);
priority_queue<int,vector<int>,greater<int> > heap; //heap为小根堆,存储每个组右端点的最大值
for(int i=0;i<n;i++){
int l = range[i].l, r = range[i].r;
if(heap.empty() || heap.top() >= l){ //创建新组
heap.push(r);
}else{ //加入到最小组并更新右端点
heap.pop();
heap.push(r);
}
}
cout<<heap.size()<<endl; //输出组数
return 0;
}
思路:按照区间左端点进行排序,枚举每个区间。如果当前区间左端点大于某个组右端点,将该区间加入到这个组,并更新该组的右端点。否则,如果没有任何组满足,创建新的组。
区间覆盖
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 10;
int n,st,ed,ans;
struct Range{
int l,r;
}range[maxn];
bool cmp(Range a,Range b){
return a.l < b.l;
}
int main(){
cin>>st>>ed>>n;
for(int i=0;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
range[i] = {a,b};
}
sort(range,range + n,cmp);
bool flag = false;
for(int i=0;i<n;i++){ //利用双指针查找每次小于左端点且右端点最大的区间,双指针时间复杂度是O(n).
int j = i,r = -1 * 0x3f3f3f3f;
while(j < n && range[j].l <= st){
r = max(r,range[j].r);
j++;
}
if(r < st){
ans = -1;
break;
}
ans ++;
if(r >= ed){ //只有r >= end 才完成了区间覆盖
flag = true;
break;
}
st = r;
i = j - 1;
}
if(flag) cout<<ans;
else puts("-1");
return 0;
}
思路:按照左端点排序,选择小于目标区间左端点且右端点最大的区间。更新目标区间的左端点。