贪心问题(一) ---- 区间问题

贪心问题有一个特点:
按照这个方案可以达到局部最优解,每次取得局部最优而达到全局最优。

贪心问题虽然没有很明显的套路,但是我们可以试着尝试一些做法:
对于区间问题,无外乎就是一些排序(左端点排序、右端点排序、双关键字排序)
举一些栗子, 看看是否合理,感受一下。
在这里插入图片描述

区间选点

题目描述:
在这里插入图片描述
题目链接

思路:
在这里插入图片描述

  1. 排序:我们按照区间右端点排序
  2. 从前往后依次枚举每个区间:
    对于每个区间,我们尽可能选最右边的点,这样这点尽可能的在多个区间内。
    (1)如果当前区间已经包含点。直接pass
    (2)否则。选择当前区间的右端点

证明:
(1)设最优解为要选ans个,此方案需选择cnt个点。
(2). 每个区间一定包含一个点,该方案是合法方案。ans <= cnt
(3)当选择当前区间右端点时,这个区间一定是和前面的区间无交集的, 则在所有方案数中该区间一定会选择这个点。ans >= cnt
因此 ans = cnt, 此为最优方案。

参考ac代码:

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

typedef struct Node{
    int l, r;
}node;
node arr[N];

bool cmp(node a, node b){
    return a.r < b.r;
}

int main(void){
    int n;
    cin >> n;
    for(int i = 0; i < n; i ++){
        cin >> arr[i].l >> arr[i].r;
    }
    sort(arr, arr+n, cmp);
    int res = 0;
    int pre_ed = -2e9; 
    //表示该区间的前一个所选点,第一个区间的右端点必选,可初始化为负
    for(int i = 0; i < n; i ++){
        if(arr[i].l > pre_ed){
            res ++;
            pre_ed = arr[i].r;
        }
    }
    cout << res ;
    return 0;
}

最大不相交区间数量

题目描述:
在这里插入图片描述
题目链接

思路:

  1. 先将每个区间的右端点,从小到大排序
  2. 从前往后一次枚举每个区间的右端点,若下个区间包含该点,则直接pass, 否则,选择该区间右端点, cnt++。

证明:
(1)设最优解为要选ans个,此方案需选择cnt个点。
(2)我们这样选择的区间间时没有交集的,该方案为一种合法方案, ans >= cnt
(3) 在此想证明ans <= cnt, 则由(2)知ans >= cnt, 则ans = cnt, 此为最优方案。
反证法,假设ans > cnt:
假设我们可以选择比cnt+x不相交的区间,每个区间都至少要包括一个所选点,那我们至少要选出来cnt + x个点才能覆盖掉所有区间,
由于我们选择了cnt个点,所以矛盾, ans <= cnt。

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

typedef struct Node{
    int l, r;
}node;
node arr[N];

bool cmp(node a, node b){
    return a.r < b.r;
}

int main(void){
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; i ++){
        scanf("%d%d", &arr[i].l,&arr[i].r);
    }
    sort(arr, arr + n, cmp);
    int cnt = 0;
    int pre_ed = -2e9;
    for(int i = 0; i < n; i ++){
        if(arr[i].l > pre_ed){
            pre_ed = arr[i].r;
            cnt ++;
        }
    }
    printf("%d", cnt);
    return 0;
}

区间分组

问题描述:
在这里插入图片描述
题目链接

思路:
在这里插入图片描述

  1. 按照左端点从小到大排序,
  2. 从前往后处理每个区间,判断能否将其放到某个现有的组中:
    每一组最靠右的点是不是大于等于当前区间左端点:
    (1)若是,L[i] <= Max_r, 则有相交部分, 开一个新组,并把他放进去。(若有多组,随便放一个就行)
    (2) 否则,L[i] > Max_r,可以归为一组。更新当前组的Max_r

证明:
(1)设最优解为要选ans个,此方案需选择cnt个点。
(2)此方案分得的组中,各区间没有交集。是为合法方案,ans <= cnt
(3) 所有有公共点的区间一定都得分开,若不是此方案数,则一定大于cnt, ans >= cnt
因此 cnt == ans, 为最优解。

此处,我们还用了一个小根堆来维护所有组中右端点的最大值。
关于堆的一篇博客

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

typedef struct Node{
    int l, r;
}node;
node arr[N];

bool cmp(node a, node b){
    return a.l < b.l;
}

int main(void){
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; i ++){
        cin >> arr[i].l >> arr[i].r;
    }
    sort(arr, arr + n, cmp);
    priority_queue<int, vector<int>, greater<int>> heap;   
    for(int i = 0; i < n; i ++){
        auto t = arr[i];
        if(heap.empty() || heap.top() >= t.l){
            heap.push(t.r);
        }
        else {
            int p =  heap.top();
            heap.pop();
            heap.push(t.r);
        }
    }
    printf("%d", heap.size());
    
    return 0;
}

区间覆盖

题目描述:
在这里插入图片描述
题目链接

思路:
在这里插入图片描述

  1. 所有区间按左端点从小到大排序
  2. 从前往后依次枚举每个区间,在能够覆盖左端点(start)的区间中选择一个右端点最大的区间
    选完后,将start更新为右端点最大值

证明:

只需要仅仅把握住cnt选区间的条件,会发现最优解中的区间都可替换成此方案选择的区间,且不会增加区间数量(可以画图感受一下),且两者ans == cnt.

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

typedef struct Node{
    int l, r;
}node;
node arr[N];

bool cmp(node a, node b){
    return a.l < b.l;
}

int main(void){
    int n, st, ed;
    cin >> st >> ed;
    cin >> n;
    for(int i = 0; i < n; i ++){
        cin >> arr[i].l >> arr[i].r;
    }
    
    sort(arr, arr + n, cmp);
    
    int res = 0;
    bool book = false;
    for(int i = 0; i < n; i ++){
        int j = i, r = -2e9;
        while(j < n && arr[j].l <= st){
            r = max(r, arr[j].r);
            j ++;
        }
        if(r < st){
            res = -1;
            break;
        }
        res ++;
        
        if(r >= ed){
            book = true;
            break;
        }
        i = j - 1;
        st = r;
    }
    if(!book) res = -1;
    cout << res;
    
}

还是要多多思考的。
在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Xuhx&

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

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

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

打赏作者

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

抵扣说明:

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

余额充值