Acwing 算法基础课——区间系列问题(基于贪心算法的选点

 905. 区间选点

 

 

该题,翻译一下,就是找到给出的区间中,把有交集的并为一组,已经并一组的集合不能再次合并。

按照贪心去贪,就是将选中的各个区间按照左端点小->大排序,然后标记区间的右端点,当下一个区间的左端点要大于标记的右端点,就要开下一个区间。

对于每个区间的左右端点,

要是该区间的左端点要大于标记的右端点的值的时候,就要新开一个,并且更新该标记的右端点

要是该区间的右端点要于标记的右端点的值的时候,标记的右端点就要更新。

复杂度为O(n);

复制代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
vector<pair<int,int>>ve;
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        int l,r;
        cin>>l>>r;
        ve.push_back({l,r});
    }
    sort(ve.begin(),ve.end());
    int cnt=1;
    int Or=ve[0].second;
    //for(int i=0;i<n;i++) cout<<ve[i].first<<" "<<ve[i].second<<endl;
    for(int i=1;i<n;i++){
        int l=ve[i].first;
        int r=ve[i].second;
        if(l>Or){
            cnt++;
            Or=r;
        }
        else{
            if(r<Or) Or=r;
        }
    }
    cout<<cnt;
}
复制代码

 

 

908. 最大不相交区间数量

 

      对于该题来说,如题意就是找其中的最大不相交的区间的个数

       对于区间的排序,最开始我想的是按照左端点从小到大排序(也就是拿到区间类问题的一般处理数据的想法,记录右端点,要是下一个区间的左端点要比记录的大,

就加进来,并且更新记录的右端点

    但是左端点有个弊端:

 

 

    如该样例,如果按照左端点去排序,然后记下右端点,那么第一个的右端点就覆盖了下面很多个区间,该算法最后的出来只能是1,而实际上是2;

故按照左端点来排列的话,区间的长度会影响最终答案的大小。

   当把右端点从小到大排列好后,不论你区间长度多大(也就是说,不论你左边会延长到哪里,都不会影响下一个区间的选择判断;

   那么算法就是,把区间按照右端点从小到大排序,标记右端点的值,对于每一个区间,读取左端点,要是左端点要比标记的右端点的值大,就加入进答案,然后更新标记的右端点值

复杂度为O(n);

复制代码
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<pair<int,int>>v;
bool cmp(pair<int,int>a,pair<int,int>b){
    return a.second<b.second;
}
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        int l,r;
        cin>>l>>r;
        v.push_back({l,r});
    }
    sort(v.begin(),v.end(),cmp);
    int Or=v[0].second;
    int cnt=1;
    for(int i=1;i<n;i++){
        if(v[i].first>Or){
            cnt++;
            Or=v[i].second;
        }
    }
    cout<<cnt;
}
复制代码

 

906. 区间分组

 

  该题就是把区间根据左端点从小到大排列好后,对于区间存入时,找没有重合的区间进行组合,也就是在前面的区间中,找比当前左端点小的右端点,然后加入到该端点中,并且更新该右端点,要是找不到就自己重新开一个,此时要加入当前的右端点。为了贪心,找的应该是前面最小的那个右端点。

  暴力的思路,就是去找前面的最小右端点的时候,一个一个去找,是O(n)级别的复杂度。对于本题会超。

  若是用优先队列的小根堆,每次在顶端的就是最小的右端点,而当前的左端点只需要判断即可。根堆的复杂度是O(logN),可以降下复杂度;

复制代码
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
vector<pair<int,int>>v;
int main()
{
    priority_queue<int,vector<int>,greater<int>>q;//小根堆,每次在顶端的是最小的,排序的复杂度为O(logN)
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        int l,r;
        cin>>l>>r;
        v.push_back({l,r});
    }
    sort(v.begin(),v.end());//按照左端点从小到大排序
    q.push(v[0].second);
    for(int i=1;i<n;i++){
        int r=q.top();
        if(r>=v[i].first){
            q.push(v[i].second);
        }else{
            q.pop();
            q.push(v[i].second);
        }
    }
    cout<<q.size();
}
复制代码

 

907. 区间覆盖

 

 

 对于该题来说,先将需要选择的区间,按照左端点从小到大排序,对于每一个区间,当前的左端点要比需要选择的左端点要小的时候,遍历满足条件的所有区间,并且选出其中右端点最大的,作为下一个需要选择的左端点。

  其中的一些细节:

 记得对区间进行排序

 防止区间选完了,但是其实不够覆盖的情况

 防止区间已经完全覆盖了,但是还继续选择的情况。

复制代码
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<pair<int,int>>v;
int main()
{
    int n,s,t;
    cin>>s>>t;
    cin>>n;
    for(int i=0;i<n;i++){
        int l,r;
        cin>>l>>r;
        v.push_back({l,r});
    }
    sort(v.begin(),v.end());
    int Or=s,ans=0,fr;
    for(int i=0;i<n;i++){
        if(v[i].first<=Or){//可以进行选了,选后面左端点小于Or中,右端点最大的
            fr=v[i].second;
            //cout<<fr<<endl;
            for(int j=i+1;j<n;j++){
                if(v[j].first>Or) break;
                fr=max(fr,v[j].second);
                i=j;
            }
            if(fr>=Or) ans++;
            Or=fr;
        }
        if(Or>=t) break;//要是当前选的区间右端点已经够了,就跳出来,不用继续选。
    }
    if(ans==0||fr<t) cout<<"-1";//fr<t是为了防止一种情况,就是能选的都选完了,但是还是不够
    else cout<<ans;
}
复制代码

 

 总的来说,区间问题就是先把区间按照左/右排序,在有序的情况下,对于每一种选择都要做到最优的情况,从而达到贪心的目的。

 

 

 

 

 

 

 

 

 

posted @   dueyu  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示