前言:
贪心就是在局部范围内求得最优解以求在全局范围内取得最优解。但是这只是理想情况,还有许多不理想情况,这是我们就要判断此时贪心法的正确性。判断贪心法的方法有很多:如数学法,反证法........但是当你不会用这些方法来证明你的贪心是正确的时候,亦可以借助你的直觉,多做些贪心的题目这种直觉会越发准确。
但是有一部分题目看起来像贪心但又不是贪心,就比如一些背包问题dp,所以贪心最重要的的是要判断这道题是不是贪心,这里可以用暴力求解,多想几个数据,看看贪心的方法是不是最优的(俗称:手动暴力)。
总之,贪心 = 判断贪心+贪心策略
接下来就来几道例题看看贪心的奥秘!!
例题:
t1:区间选点
代码思路:
1、其实本意就是看看有几个区间重合不与某个或某几个区间重合,只有让几个区间重合的的部分放上点才是最好的贪心策略,使得总点数最少。(思路很好想明白,就看马力了)
2、具体思路就是按照右端点进行排序,如果我另一个区间的左端点<=此时的这个最右.右端点则是重合的不用多放点,如果不是就要在从新放置一个点,更新最右.右端点为这个区间的右端点。
代码:
#include<bits/stdc++.h>
using namespace std;
struct rec{
long long r,l;
} a[1000000];
bool cmp(rec a,rec b){
return a.r < b.r;
}
int main(){
int n; cin >> n;
int cnt = 1;
for(int i = 1; i <= n; i++) cin >> a[i].l >> a[i].r;
sort(a+1,a+1+n,cmp);
int x = a[1].r;
for(int i = 2; i <= n; i++){
if(a[i].l <= x) continue;
else {
cnt++;
x = a[i].r;
}
}
cout << cnt << endl;
return 0;
}
t2:区间分组:
代码思路:
1、这道题看似跟上道题有点相似其实他两个不太一样,因为这个区间可能跟好几个区间左右有重合所以不可以参考上面的方法。
2、这道题的贪心思路是我从头开始遍历如果新的区间会跟某一组重合那么就新开一组,如果不会重合就把他加入到某一组里。
3、具体代码实现就是将区间按照左端点进行排序,如果新的区间的左端点<最小的右端点,说明应该从新开一组并且把这一组的左端点加入小根堆进行排列,让后每次的最小右端点就取堆顶,如果不重合就要删去堆顶元素跟新右端点。(默认加入堆顶元素的那一组所以要先删去元素)
代码:
#include<bits/stdc++.h>
using namespace std;
struct rec{
int l,r;
} a[100000];
bool cmp(rec a,rec b){ //按照左端点从小到大排序
return a.l < b.l;
}
priority_queue<int,vector<int>,greater<int> > q; //创建小根堆,储存最小的右端点
int main(){
int n,cnt = 0; cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i].l >> a[i].r;
sort(a+1,a+n+1,cmp);
for(int i = 1; i <= n; i++){
if(q.size() == 0 || a[i].l <= q.top()){ //如果此时为空就要开一组,不然没有组
cnt++; //如果重合原先的每一组组则要从新开一组
q.push(a[i].r);
}
else{
//如果没有重合就要更新此时的右端点
q.pop();
q.push(a[i].r);
}
}
cout << cnt << endl;
return 0;
}
t3:区间覆盖:
代码思路:
1、左端点排序,找出l<=指定区间l 线段,且r要最大,如果没有则表示覆盖不了返回-1,如果有但是这个区间没有完全覆盖则从这个区间继续找能覆盖这个区间的线段,此时这个区间l要跟新为刚刚的r表示已经覆盖过了只要覆盖剩下的区间就好了,如果这个线段的r超过了区间的r表示覆盖完了输出覆盖所用的线段数
代码:
#include<bits/stdc++.h>
using namespace std;
int n,s,t,cnt = 0;
struct rec{
int l,r;
} a[1000000];
bool cmp(rec a,rec b){ //左端点从小到大排序
return a.l < b.l;
}
int main(){
cin >> s >> t >> n;
for(int i = 1; i <= n; i++) cin >> a[i].l >> a[i].r;
sort(a+1,a+n+1,cmp);
for(int i = 1; i <= n; i++){
int j = i,r = -1e9;
while(j <= n && a[j].l <= s){ //寻找l <= s的最长线段的r
r = max(r,a[j].r);
j++;
}
if(r < s){ //连区间开头 (l)都覆盖不了,表示不能覆盖返回-1
cnt = -1;
break;
}
cnt++; //线段数++
if(r >= t) break; //覆盖完了结束循环
i = j-1; //从当前线段找起
s = r; //更新需要覆盖区间的起始点
}
cout << cnt << endl;
return 0;
}
总结:
贪心还需要勤加练习才可以掌握,加油!!