区间贪心
- 简单介绍
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
简单来说就是以局部最优换取整体最优,相信大家对贪心算法都不陌生吧,下面,列举几道关于区间贪心的题目
-
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;
}
总结
本篇博客中,列举了三种经典的区间贪心问题
已知区间队列
- 求不相交区间的最大数目
- 将不相交区间归为同一类,且使得类别数最小
- 求相交区间的最小数目
基本是按照左右端点的大小进行排序,然后分治成一个个子问题,求子问题最优解从而得到总问题的最优解
另外,还需注意,不是所有的最优化问题都可以用贪心处理,贪心需要证明。