区间选点

原题链接:905. 区间选点 - AcWing题库

在所给的多个区间里选点,使得选得最少的点覆盖所有区间 端点也算

思路

先对样例试一试

从当前看哪种情况最佳? 一个点覆盖最多的区间 选哪个点可以覆盖最多区间? 只要区间不完全分开,就可以选有重叠的区间的右端点来覆盖多个区间

实现

#include <iostream>
#include <algorithm>
 
using namespace std;
 
const int N = 1e5 + 10;
 
//利用重载,也可以写cmp
struct Range{
    int l, r;
    bool operator < (const Range &w) const
    {
        return r < w.r;
    }
}r[N];
 
 
int main()
{
    int n;
    cin >> n;
    
    for(int i = 0; i < n; i ++)
    {
        cin >> r[i].l >> r[i].r;
    }
    
    //按右端点排序
    sort(r, r + n);
    
    int rd = -2e9, ans = 0;
    for(int i = 0; i < n; i ++)
    {
        if(r[i].l > rd)
        {
            ans ++;
            rd = r[i].r;
        }
    }
    
    cout << ans << endl;
    
    return 0;
}

另一种说法

原题链接:908. 最大不相交区间数量 - AcWing题库

几乎一致的思路 要选最大的不交叉区间,则区间的左端点不能小于前一个的右端点,要尽可能多,就从头开始选(贪心)

区间分组

原题链接:906. 区间分组 - AcWing题库

对所给区间分组,要求没有交集,分组尽可能少

思路

贪心的做

对区间排序,按需求,我要将尽可能多的区间分为一组,那么遍历时应该是考虑尽可能多的区间能不能并到现有的组里,也就是看左端点与当前的右端点有没有交集,要尽可能多,那就左端点从小看到大,找可以合并成一组的——左端点排序

然后考虑贪心着做对不对

从两个角度看,但凡能有两种特殊情况满足上述式子,就可以证明大于等于小于等于都成立

首先找的是最小的分组数,则 一定成立

特殊情况证明大于等于: 当每个区间都要各分一组的时候,即下一个的左端点都小于当前的右端点 由于 ans 一定是所有的区间的分组数,其值一定得是大于等于所有区间数才可能给所有区间分组,所以 得证

实现

要维护一个数组,用来存下每组的右端点值,同时数组的大小就是分组数

小根堆

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
 
using namespace std;
 
const int N = 1e5 + 10;
 
struct Range{
    int l, r;
    //左端点排序
    bool operator < (const Range &w)const{
        return l < w.l;
    }
}r[N];
 
int main()
{
    int n;
    cin >> n;
    
    for(int i = 0; i < n; i ++)
    {
        cin >> r[i].l >> r[i].r;
    }
    
    sort(r, r + n);
    //小根堆
    priority_queue<int, vector<int>, greater<int>> heap;
    
    for(int i = 0; i < n; i ++)
    {
	    //如果左端点小于最小右端点或还没分组
        if(heap.empty() || r[i].l <= heap.top())
        {
            //建新组
            heap.push(r[i].r);
        }
        else
        {
            //加到当前右端点最小的组
            heap.pop();
            heap.push(r[i].r);
        }
    }
    
    cout << heap.size() << endl;
    
    return 0;
}

实际上实现可以可以是任选一组添加,这里实现为加到最小的一组

  • 更符合贪心思想
  • 其实没有证明任何一组都可以

区间覆盖

原题链接:907. 区间覆盖 - AcWing题库

给定可选区间与要求区间,要求找最少的区间覆盖要求区间

思路

贪心着做

每次选肯定要选能覆盖最多的,那就按左端点排序,选右端点到最远的,同时与前面选的区间有交集的区间

证明

小于等于:所选必是所有可能选法之一,答案要求最小,必然小于等于

大于等于: 在第一处不一样的地方——如果答案选了右端点比思路选的近的(不可能远,因为思路选最远),那么其后面要是有更优的选法,思路当前选的这个区间一定能覆盖到之后选的区间的左端点,而且每次都可以

  • 选短再选短,则思路选法更优
  • 选短再选长,必覆盖得到,思路更优
  • 都选长,则与思路一致 理解着明白大于等于

实现

  1. 左排序
  2. 找左端点小于等于当前右端点的右端点最大的区间
  3. 更新右端点
  4. 用双指针找
#include <iostream>
#include <algorithm>
 
using namespace std;
const int N = 1e5  +10;
 
 
struct Range{
    int l, r;
    bool operator < (const Range &w)const{
        return l < w.l;
    }
}r[N], ed;
 
 
 
int main()
{
    cin >> ed.l >> ed.r;
    
    int n;
    cin >> n;
    
    for(int i = 0; i < n; i ++)
    {
        cin >> r[i].l >> r[i].r;
    }
    
    sort(r, r + n);
    
    int cnt = 0, st = ed.l;
    bool flag = 0;
    for(int i = 0; i < n; i ++)
    {
        int j = i, now_r = -2e9;
        //起始满足左端点小于区间左端点,然后选最长
        while(j < n && r[j].l <= st)
        {
            now_r = max(now_r, r[j].r);
            j ++;
        }
        
        //如果交不上了
        if(now_r < st)
            break;
        
        if(now_r >= ed.r)
        {
            flag = 1;
            cnt ++;
            break;
        }
        
        cnt ++;
        //双指针,当前j找过的都不用找
        i = j - 1;
        st = now_r;
    }
    
    if(flag) cout << cnt << endl;
    else  cout << "-1" << endl;
    
    return 0;
}