贪心:区间问题

 区间选点:

1、将每个区间按照右端点从小到大进行排序

2、从前往后枚举区间,end值初始化为无穷小

    如果本次区间不能覆盖掉上次区间的右端点, ed < range[i].l

    说明需要选择一个新的点, res ++ ; ed = range[i].r;

如果本次区间可以覆盖掉上次区间的右端点,则进行下一轮循环

#include 
#include 

using namespace std;

const int N = 100010;

int n;
struct Range//区间范围
{
    int l, r;//左右端点
    bool operator< (const Range &W)const//重载<,用右端点排序,从小到大
    {
        return r < W.r;
    }
}range[N];

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

    sort(range, range + n);//升序,从小到大

    int res = 0, ed = -2e9;//res是当前选择的点的数量,ed是上一个点(初始化为负无穷)
    
    for (int i = 0; i < n; i ++ )//枚举所有区间
        if (ed < range[i].l)//如果上一个点小于这个区间的左端点
        {
            res ++ ;//点的数量+1
            ed = range[i].r;//选择新的点
        }

    printf("%d\n", res);

    return 0;
}

最大不相交区间数量:

代码与上题完全一样:

上题求的是最少要用几个点让每个区间都有点,即不相交的区间个数。

#include 
#include 

using namespace std;

const int N = 100010;

int n;
struct Range//区间范围
{
    int l, r;//左右端点
    bool operator< (const Range &W)const//重载<,用右端点排序,从小到大
    {
        return r < W.r;
    }
}range[N];

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

    sort(range, range + n);//升序,从小到大

    int res = 0, ed = -2e9;//res是当前选择的点的数量,ed是上一个点(初始化为负无穷)
    
    for (int i = 0; i < n; i ++ )//枚举所有区间
        if (ed < range[i].l)//如果上一个点小于这个区间的左端点
        {
            res ++ ;//点的数量+1
            ed = range[i].r;//选择新的点
        }

    printf("%d\n", res);

    return 0;
}

区间分组:

贪心决策

从前往后枚举每个区间,判断此区间能否将其放到现有的组中

    如果一个区间的左端点比最小组的右端点要小,ranges[i].l<=heap.top() , 就开一个新组 heap.push(range[i].r);

    如果一个区间的左端点比最小组的右端点要大,则放在该组, heap.pop(), heap.push(range[i].r);

    每组去除右端点最小的区间,只保留一个右端点较大的区间,这样heap有多少区间,就有多少组。

#include
#include
#include
using namespace std;

const int N=100010;

int n;
struct Range
{
    int l,r;
    bool operator <(const Range &W)const//重载<,用左端点从小到大排序
    {
        return l,greater>heap;//小根堆,自动把最小值放到根节点,这里放入的是右端点
    
    for (int i = 0; i < n; i ++ )
    {
        //如果堆为空,或者堆的右端点大于i的左端点,则需要开一个新空间给i区(因为区间不能重合)
        if (heap.empty() || heap.top() >= range[i].l)
        {
            heap.push(range[i].r);
        }
        //如果可以放进来(即i的左端点大于该堆的右端点),则更新堆的右端点(不要上次更新的右端点,用这次的,下面注释有解释)
        else
        {
            heap.pop();
            heap.push(range[i].r);
        }
        //例:[1,2],[3,4],这两个可以合并=[1,4],top=4
        //    [2,3],则不能合并=[2,3][1,4](小跟堆按右端点排序),top=3
        //    [4,5],不用看右端点更大的[1,4],只用看跟的右端点,因为跟的右端点是最小的,不可能小于跟的右端点反而能大于子的右端点
        //因为range也从小到大排序了,所以不用担心i比i+1大,导致根可以合并i+1再合并i却因为合并了i而变大无法合并i+1
        //所以只看[2,3]即可合并=[1,4][2,5],top=4.下次又是用[1,4]来看了
    }

    printf("%d\n", heap.size());

    return 0;
}

区间覆盖:

#include 
#include 

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;//双指针遍历,j是第几个区间,r是新区间的右端点
        while (j < n && range[j].l <= st)//遍历所有区间的左端点在start的左边,并且右端点最大的
        {
            r = max(r, range[j].r);
            j ++ ;
        }

        if (r < st)//如果没有右端点在start右边的,说明无解,直接出去
        {
            res = -1;
            break;
        }

        res ++ ;//选择区间+1
        if (r >= ed)//如果当前的右端点大于end,说明已经全覆盖了线段区间了,直接出去
        {
            success = true;
            break;
        }

        st = r;//如果选了区间,并且当前还未全覆盖,则让start为新的右端点,在此后面找区间
        i = j - 1; //因为已经选了新区间j了,下次从j+1开始找。又因为上面j++最后多加了一次,而且i会++,所以i=j-1
    }

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

    return 0;
}

你可能感兴趣的:(算法基础,算法,c++,数据结构)