贪心算法——区间合并以及覆盖问题

贪心算法的核心在于,只是注意当前的最优解情况,每次依次选择最优解,使得最终问题得到最优解。一般来说,贪心算法只能解决单峰问题。

而具体到贪心的区间问题,做法一般比较固定。首先先将各个区间按照左或者右端点从小到大或者从大到小排序,然后进行逐个把区间的左或者右端点进行比较,判断选择的区间是否与当前区间相交或者不相交,在区间合并的过程中,可能要运用小根堆等数据结构(如果不熟悉的可以先去了解)。

先来看一道经典例题。

在这里插入图片描述

其实贪心问题往往是先想到解决方法,再去证明的。至于证明方法我由于水平有限,就不做证明了(这里提一下,常见的证明方法可以采用反证法以及数学归纳法)

贪心决策

先将每个区间按左区间从大到小排序,然后从前往后枚举每个区间,判断此区间能否将其放到现有的组中,如果不能就创建新的组。最后组的数目就是答案

1.当前区间的左端点比最小组右端点小,说明两区间有交集,就要开一个新的组。
2. 当前区间的左端点比最小组右端点大,说明两区间没有交集,因此放入该组,并且将该组的右端点更新成加入区间的右端点。
在这里插入图片描述

至于为什么要取最小组,这是因为只有放进了最小组,才有更多的空间留给别的组。这里我们举个例子。

在这里插入图片描述

我们来看一下代码

#include<bits/stdc++.h>

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()
{
    
    
    cin >> n;

    for(int i = 0 ; i < n ; i++)
    {
    
    
        int l , r;

        scanf( "%d%d" , &l , &r );

        range[i] = {
    
     l , r };
    }
	//这里将所有区间按左端点从小到大排序
    sort( range , range + n );
	//这里建立一个小根堆,小根堆的最顶部是最小的数,由小到大依次往下
    priority_queue< int , vector<int> , greater<int> > heap;

    for(int i = 0 ; i < n ; i++)
    {
    
    
        auto r = range[i];
        //这个判断当无小组或者最小组的右端点比当前区间的左端点要小,此时将区间插入最小组就行了
        if( heap.empty() || heap.top() >= r.l ) heap.push( r.r );
        //否则就要增加一个组
        else
        {
    
    
            heap.pop();
            heap.push( r.r );
        }
    }

    cout << heap.size() << endl;

    return 0;
 } 

作者:阿柴
链接:https://www.acwing.com/activity/content/code/content/549506/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

再来看一道经典例题。

在这里插入图片描述

贪心决策

在这里插入图片描述

  1. 将所有区间按照左端点从小到大进行排序
  2. 从前往后枚举每个区间,在所有能覆盖start的区间中,选择右端点的最大区间,然后将start更新成右端点的最大值
#include<bits/stdc++.h>

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;

    cin >> st >> ed >> 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;

        //这个循环是找出一个区间,这个区间比st小,但是它的右端点又是最大的(比st小的最长区间) 
        while( j < n && range[j].l <= st )
        {
    
    
            r = max( r , range[j].r );
            j++;
        }

        //这里判断如果选出的最长区间的右端点还是比st小,证明无法包含目标区间的左端点部分 
        if( r < st )
        {
    
    
            res = -1;
            break;
        }

        //经过上面判断,证明了区间左端点比st小并且包含st,于是把这个区间加入进去 
        res++;

        //这里判断当区间已经包含了ed,证明了已经覆盖了目标区间 
        if( r >= ed )
        {
    
    
            success = true;
            break;
        }

        //当还没覆盖目标区间,此时将r(最后一个区间的右端点)赋值给st 
        st = r;
        //这里是从第j - 1 个开始遍历,因为最后进行了j++,所以要减回去 
        i = j - 1;
    }

    if( !success ) res = -1 ;
    cout << res << endl;

    return 0;
}

作者:阿柴
链接:https://www.acwing.com/activity/content/code/content/553929/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

猜你喜欢

转载自blog.csdn.net/MrChen666/article/details/109292679