【笔记】贪心(1)---区间问题


区间问题

贪心算法解决区间问题三步走:

  1. 创建Range结构体;
  2. 按照左端点或右端点对区间进行排序;
  3. 利用贪心算法求解。

一、区间选点

给定 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。

对于当前循环的区间,我们只考查它的左端点,因此按照左端点排序。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值