贪心算法的核心在于,只是注意当前的最优解情况,每次依次选择最优解,使得最终问题得到最优解。一般来说,贪心算法只能解决单峰问题。
而具体到贪心的区间问题,做法一般比较固定。首先先将各个区间按照左或者右端点从小到大或者从大到小排序,然后进行逐个把区间的左或者右端点进行比较,判断选择的区间是否与当前区间相交或者不相交,在区间合并的过程中,可能要运用小根堆等数据结构(如果不熟悉的可以先去了解)。
先来看一道经典例题。
其实贪心问题往往是先想到解决方法,再去证明的。至于证明方法我由于水平有限,就不做证明了(这里提一下,常见的证明方法可以采用反证法以及数学归纳法)
贪心决策
先将每个区间按左区间从大到小排序,然后从前往后枚举每个区间,判断此区间能否将其放到现有的组中,如果不能就创建新的组。最后组的数目就是答案
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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
再来看一道经典例题。
贪心决策
- 将所有区间按照左端点从小到大进行排序
- 从前往后枚举每个区间,在所有能覆盖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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。