贪心法学习笔记

区间选点

在这里插入图片描述
在这里插入图片描述
意思就是从比如上图这是有四个区间,这些区间可能有重合的部分,从这些区间中选出最少数目的点,存在于所有区间之中

这个题的想法:

  1. 将每个区间按照右端点的大小从小到大排序
    在这里插入图片描述

  2. 然后从前到后从每个区间选一个点,显然选区间右端点覆盖到其他区间的可能性更大,然后依次看每个区间有没有已经被覆盖到了,如果覆盖到了则继续看下一个区间,如果没有覆盖到,那么就再在这个区间中选一个点

那么这个方法是否是正确的那?应该怎么来证明那?
证明: 首先我们设 Ans为可行解的最小值(即覆盖所有区间的满足的最少数目的点)
Cnt为能够满足题意的一个可行解
那么,显然有 Ans ≤ Cnt
然后,我们在选一个点的时候,在选下一个点的时候,我们选的此时这个点所在的那个区间一定不会与上一个点所能在的几个区间有交集,那么其实这样就是可以抽象成我们找到了Cnt个相互没有交集的区间(那些有交集的区间在我们选了一个在这些区间中的点时我们就把这几个区间的并集看成一个区间这么来思考) , 那么我们想要把所有区间都覆盖掉,那么我们就至少需要Cnt个点,所以我们就得到了Ans ≥ Cnt
综上:我们就得到了Ans = Cnt 也就是说,只要我们用这种方法找到了一个可行解,那么这个解也一定是最优解

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;

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++){
        int l,r;
        cin >> l >> r;
        range[i] = {l,r};
    }
    sort(range,range+n);
    int res = 0;  //表示选择的点的个数
    int last_point = -2e9;  //上一个选择的点在数轴上的下标
    for(int i=0;i<n;i++)
    {
        if(range[i].l > last_point){
            res++;
            last_point = range[i].r;
        }
        
    }
    cout << res << endl;
    return 0;
}

最大不相交区间数量

在这里插入图片描述
这个题和上一个题的思路是完全一样的做法

#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1e5+10;
int n;

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;
    }
    sort(range,range+n);
    int res = 0;
    int last_node = -2e9;
    for(int i=0;i<n;i++)
    {
        if(range[i].l > last_node){
            res++;
            last_node = range[i].r;
        }
    }
    cout << res;
    return 0;
    
}

区间分组

在这里插入图片描述
解决方法:

  1. 将所有区间按照左端点从小到大排序
  2. 从前向后处理每个区间,对于每个区间看看这个区间能不能放到当前现有的某个组中
    如果不存在这样的组,那么就得开一个新的组,再把这个区间放进去
    如果存在这样的组,就将他放到这个组中,更新下这个组的右端点的最大值max_r
    证明:
    设Ans为满足条件的需要的最小组数,Cnt为按照上述方法得到的一个组的数量
    1 显然 Ans ≤ Cnt
    2 假设现在已经找到了Cnt-1个分组,当我们再看第Cnt号组能不能和前面Cnt - 1个组进行和合并那?因为这个之所以会分出来这个第Cnt号组,是因为这个组里至少有一个区间与前面的各个组都有交集,也就是说Cnt号组并不能与前面Cnt-1个组进行合并,因为一定有交集,所以就得到Ans ≥ Cnt
    综上: Ans = Cnt,所以我们按照这种方法得到的解就是最优解
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int N = 1e5+10;
int n;

struct Range{
    int l,r;
    bool operator <(const Range& w)const{
        return l < w.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;//heap小根堆存的是每个集合最大的右端点值
    for(int i=0;i<n;i++)
    {
        Range now_node = range[i];
        if(heap.empty() || heap.top() >= now_node.l) heap.push(now_node.r);
        else{
            heap.pop();
            heap.push(now_node.r);
        }
    }
    cout << heap.size()<<endl;
    return 0;
}

区间覆盖

在这里插入图片描述
做法:

  1. 将各个区间从左端点从小到大排序
  2. 从前向后依次枚举每个区间,第一次选能够覆盖线段区间的左端点start并且右端点最大的那个区间,然后将start更新成选的那个区间的右端点
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
struct Range
{
    int l, r;
    bool operator< (const Range &W)const
    {
        return l < W.l;
    }
}range[N];

int main()
{
    int st, ed;
    scanf("%d%d", &st, &ed);
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        range[i] = {l, r};
    }

    sort(range, range + n);

    int res = 0;
    bool success = false;
    for (int i = 0; i < n; i ++ )
    {
        int j = i, r = -2e9;
        while (j < n && range[j].l <= st)
        {
            r = max(r, range[j].r);
            j ++ ;
        }

        if (r < st)
        {
            res = -1;
            break;
        }

        res ++ ;
        if (r >= ed)
        {
            success = true;
            break;
        }

        st = r;
        i = j - 1;
    }

    if (!success) res = -1;
    printf("%d\n", res);

    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

新城里的旧少年^_^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值