简单贪心_区间贪心

区间贪心

- 简单介绍

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
简单来说就是以局部最优换取整体最优,相信大家对贪心算法都不陌生吧,下面,列举几道关于区间贪心的题目

  • problem1
    电影节

  • describe
    大学生电影节在北大举办! 这天,在北大各地放了多部电影,给定每部电影的放映时间区间,区间重叠的电影不可能同时看(端点可以重合),问李雷最多可以看多少部电影。

  • input
    多组数据。每组数据开头是n(n<=100),表示共n场电影。 接下来n行,每行两个整数(均小于1000),表示一场电影的放映区 间
    n=0则数据结束

  • output
    输出 对每组数据输出最多能看几部电影
    Sample Input
    12
    1 3
    3 4
    0 7
    3 8
    15 19
    15 20
    10 15
    8 18
    6 12
    5 10
    4 14
    2 9
    0
    Sample Output 5

  • 分析:这是一个区间贪心问题,给出区间,求不相交的区间最大数量
    将所有电影按结束时间从小到大排序,第一步选结束时间最早的那部电影。 然后,每步都选和上一部选中的 电影不冲突且结束时间最早的电影。
    在这里插入图片描述
    复杂度: O(nlogn)

  • 代码

#include <iostream>
#include <algorithm>
using namespace std;
struct Movie
{
    int start,over;
};
bool cmp(Movie a,Movie b)//按照结束时间由小到大排序
{
    return a.over<b.over;
}
int main()
{
    int n;
    while(cin>>n,n!=0)
    {
        Movie movie[n];
        for(int i=0;i<n;i++) cin>>movie[i].start>>movie[i].over;
        sort(movie,movie+n,cmp);
        int ans=1,last=movie[0].over;//ans计数,last代表上一次选好的区间右端点
        for(int i=1;i<n;i++)//依次选择合法的区间
        {
            if(movie[i].start>=last)//两个区间不相交
            {
                ans++;
                last=movie[i].over;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

  • problem 2
    Stall Reservations

  • describe
    有 n头牛(1<=n<=50,000)要挤奶。给定每头牛挤奶的时间区间[A,B] (1<=A<=B<=1,000,000,A,B为整数)。牛需要呆畜栏里才能挤奶。一个畜栏同一时间只能容纳一头牛。 问至少需要多少个畜栏,才能完成全部挤奶工作,以及每头牛都 放哪个畜栏里(Special judged)去同一个畜栏的两头牛,它们挤奶时间区间哪怕只在端点重合也是不可以的。
    在这里插入图片描述

  • Input
    Line 1: A single integer, N Lines 2…N+1: Line i+1 describes cow i’s milking interval with two space-separated integers.

  • Output
    Line 1: The minimum number of stalls the barn must have. Lines 2…N+1: Line i+1 describes the stall to which cow i will be assigned for her milking period.

  • Sample Input
    5
    1 10
    2 4
    3 6
    5 8
    4 7

  • Sample Output
    4
    1
    2
    3
    2
    4

  • 分析:给出区间,求相交区间的最大数量,以及所有相交区间,即将区间归类

  • 思路一
    当然我们可以遍历所有时间点min~max,在某一个时间点,有若干个区间包含了时间点,求出最大的这个区间数
    缺点复杂度高O(n^2)

  • 思路二
    排序,直接遍历所有区间,将所有不相交的区间归为一类
    把所有奶牛按开始时间从小到大排序。
    为第一头奶牛分配一个畜栏。
    依次处理后面每头奶牛i。处理 i 时,考虑已分配畜栏中,结束时间最 早的畜栏x。
    若 E(x) < S(i), 则不用分配新畜栏,i可进入x,并修改E(x)为E(i)
    若 E(x) >= S(i),则分配新畜栏y,记 E(y) = E(i)
    直到所有奶牛处理结束
    需要用优先队列存放已经分配的畜栏,并使得结束时间最早的畜栏始终 位于队列头部。
    复杂度O(nlogn)
    优先级队列用堆实现,只是需要构建初始堆,这个时间复杂度是O(n)

  • 代码

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
struct Cow
{
    int start,over;//始末
    int id;//编号

}cows[50100];
bool cmp(Cow a,Cow b)
{
    return a.start<b.start;
}
int pos[50100];//第i只奶牛去的蓄栏编号
struct Stall
{
    int time;//结束时间
    int id;//编号
    friend bool operator<(Stall a,Stall b)
    {
        return a.time>b.time;
    }//重载,调整优先次序为由小到大
    Stall(int e,int n):time(e),id(n){};//构造函数初始化
};
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d%d",&cows[i].start,&cows[i].over);
        cows[i].id=i;
    }
    sort(cows,cows+n,cmp);
    int total=0;//蓄栏数量
    priority_queue<Stall>pq;
    for(int i=0;i<n;i++)
    {
        if(pq.empty())//空队列
        {
            total++;
            pq.push(Stall(cows[i].over,total));
            pos[cows[i].id]=total;//新增一蓄栏
        }
        else
        {
            Stall temp=pq.top();
            if(temp.time<cows[i].start)//可以进入蓄栏
            {
                pq.pop();//弹出结束时间最小蓄栏
                pos[cows[i].id]=temp.id;//蓄栏号不变
                pq.push(Stall(cows[i].over,temp.id));//压入新奶牛
            }
            else//新增蓄栏
            {
                total++;
                pq.push(Stall(cows[i].over,total));
                pos[cows[i].id]=total;
            }
        }
    }
    printf("%d\n",total);
    for(int i=0;i<n;i++) printf("%d\n",pos[i]);
    return 0;
}
/*
5

1 10

2 4

3 6

5 8

4 7 
*/

  • problem3
    Radar Installation
    describe
    x轴是海岸线,x轴上方是海洋。海洋中有n(1<=n<=1000)个岛屿,可以看作点。
    给定每个岛屿的坐标(x,y),x,y 都是整 数。当一个雷达(可以看作点)到岛屿的距离 不超过d(整数),则认为该雷达覆盖了该 岛屿。雷达只能放在x轴上。问至少需要多少个雷 达才可以覆盖全部岛屿。
    输入:多个样例
    第一行包括两个整数n,d
    第二行是n个岛屿的坐标(x,y)
    输入以0 0结束

在这里插入图片描述
Sample Input
3 2
1 2
-3 1
2 1

1 2
0 2

0 0
Sample Output
Case 1: 2
Case 2: 1

  • 分析:
    对每个岛屿P,可以算出,覆盖它的雷达,必须位于x轴上的 区间[Ps,Pe]。如果有雷达位于某个x轴区间 [a,b],称该雷达覆盖此区 间。问题转换为,至少要在x轴上放几个雷达(点),才能覆盖 全部区间[P1s,P1e],[P2s,P2e]…[Pns,Pne]
    即在x轴上选取做少的点,使得所有区间都被覆盖!

在这里插入图片描述
分解:按右端点从小到大排序后,可以选择第一个区间右端点为雷达的放置点,这时,包含的区间是最多的,不断重复这个过程,得到整体最优解;这样一来,问题就变得很简单了,和例题一是互补的两个过程;
注意,坐标转化
在这里插入图片描述
距离delta=d2-y2
delta>=0的情况下才是有效区间
如果delta<0则不存在这样的雷达,输出-1即可
区间[left,right]
left=x-sqrt(delta)
right=x+sqrt(delta)

#include <iostream>
#include <algorithm>
using namespace std;
struct Point
{
    double left,right;
};
bool cmp(Point a,Point b)
{
    return a.right<b.right;
}//排序
int main()
{
    int n,d,prime=1,Case=1;
    double x,y,delta;
    while(cin>>n>>d,n&&d)
    {
        Point point[n];//n段区间
        for(int i=0;i<n;i++)//处理坐标,转化成区间
        {
            cin>>x>>y;
            delta=d*d-y*y;
            if(delta<0) prime=0;
            else
            {
                point[i].left=x-sqrt(delta);
                point[i].right=x+sqrt(delta);
            }
        }
        if(!prime) cout<<"-1"<<endl;
        else
        {
            sort(point,point+n,cmp);//排序
            int ans=1;
            double last=point[0].right;
            for(int i=1;i<n;i++)
            {
                if(last<point[i].left)//遇到不相交的区间
                {
                    ans++;
                    last=point[i].right;
                }
            }
            cout<<"case"<<Case++<<':'<<ans<<endl;
        }
    }
    return 0;
}

总结

本篇博客中,列举了三种经典的区间贪心问题
已知区间队列

  1. 求不相交区间的最大数目
  2. 将不相交区间归为同一类,且使得类别数最小
  3. 求相交区间的最小数目
    基本是按照左右端点的大小进行排序,然后分治成一个个子问题,求子问题最优解从而得到总问题的最优解
    另外,还需注意,不是所有的最优化问题都可以用贪心处理,贪心需要证明。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值