反悔贪心(1)

有些题,容易看出用贪心算法,但是可能会遇到当前的最优解有可能是局部最优解,而不是全局最优解,这时候就要进行反悔操作。
很形象的一幅图:对于这个小山来说,确实达到了全局最优,但是整个从整个山群的状态来看,并未达到全局的最优解,也就是说如果我们没有判断到全局最优解的位置,就是个假贪心!

在这里插入图片描述
比如,这道题:
在这里插入图片描述
有截至时间和获得利润这两个变量
(1)如果按利润大小排,有一组很简单的反例:
10 10
8 8
10 3
后面两个都做得利润率肯定高于只做1的情况,此方法错误
(2)按照截止日期(从小到大)为第一关键字,价值(从大到小)为第二关键字进行排序,然后顺序遍历每个任务,能做就做,不能做(当前已安排任务数=当前任务的截止日期)就直接抛弃

但实际上有有可能到后期都是高报酬的工作(但由于前期做了太多价值很低的任务导致都超时做不了了)就会让答案不是很优秀.
如样例:
1 2 √
1 1
2 5
2 6 √
答案为8,但是如果我不做第一项,而是做第三项和第四项,最终的答案就是11,才是更优的答案。

这个很类似爬删山的问题,我找到的局部最优,但是并没有全局最优。 这就可以用反悔贪心来求解。

反悔贪心求解步骤

满足条件(即没有超出截止时间)就分成两种情况:
1.若当前的最优解比原来的最优解(堆顶)更优秀,我们就更新全局最优解,把原来的最优解丢出去,再把当前的最优解放进去(即反悔策略);
2.反之,就不管了。
不满足条件(即超出截止时间),就把当前的最优解丢进堆里,更新全局最优解即可。

总结一下就是:能放就放,放不了就把最差的踢出去。

结合这道题:
先按截至时间排序
任务一项一项来,一个时间只能干一件事,比如当前截至日期排到3了

  • 新来的一项截至日期是4,那么直接做;
  • 新来的一项截止日期是2,意味着要么不做要么已经决定要做的任务就要删除一项,基于贪心的思想,肯定是放弃那个利润最低的一项,这里即体现出反悔贪心的思路,即对于已经规划要做的,有了更好的要来,肯定要优胜劣汰,把最不好的那个踢出去,以便能够把当前最好的那个吸纳进来

在这个过程中要找做差的利润,最大最小值查找可以利用堆来完成,因此,在反悔贪心问题中,即有一种常用的方式:

  • 反悔堆

即通过堆(大根堆、小根堆)来维护当前贪心策略的最优解,若发现最优解不对,就退回上一步,更新最优解。
由于堆的性质,使得堆的首数据一定是最优的,这就可以实现快速更新最优解。

综上,本题的解题思路:按截至时间排序,遍历每一个任务,还没到截止时间,就暂时规划做它,如果该任务的截至时间<=当前的时间,就采用反悔贪心解决,优胜劣汰。

#include<bits/stdc++.h>
#define int long long
#define p first
#define d second
using namespace std;
pair<int,int>a[100005];
priority_queue <pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
bool cmp(pair<int,int>a,pair<int,int>b){
	return a.d<b.d;
}
int n,m,f=0;
signed main()
{
	cin>>n;
	for(int i=0;i<n;i++)cin>>a[i].d>>a[i].p;
	sort(a,a+n,cmp);
	for(int i=0;i<n;i++){
	   //当前的任务的截止时间已经过了 
		if(a[i].d<=q.size()) {
		//看是否有比他利润低的,堆中存储的是利润、截至时间的pair,堆中按利润维护堆的性质的
		//如果要判断的这个利润更好,就把它放进去,然后,把最差的那个剔除,同时更新答案
		if(a[i].p>q.top().p)f+=a[i].p-q.top().p,q.pop(),q.push(a[i]);}
		//没到截至时间就做
		else q.push(a[i]),f+=a[i].p;
	}
	cout<<f;
	return 0; 
} 

下一道反悔贪心的经典题

https://www.luogu.com.cn/problem/P4053

在这里插入图片描述
每一个建筑抢修,都有两个属性,一个是修的时间,一个是报废时间,参考上一题,可以按报废时间排序,如果还能修则修,不能修了,就看之前决定修的建筑中有没有维修建筑用的时间>当前这个的,肯定是修的快的更好,根据此性质进行反悔操作。

#include <iostream>
#include <algorithm>
#include <queue>
#define maxn 150010
using namespace std;

int n;
struct pii
{
	int a,b;
	bool operator < (pii temp) const
	{
		return b<temp.b;
	}	
}arr[maxn];
priority_queue<int> q;

int main()
{
	cin >> n;
	for (int i=0;i<n;i++)
	{
		int x,y;
		cin >> arr[i].a >> arr[i].b;
	}
	sort(arr,arr+n);
	long long t=0;
	for (int i=0;i<n;i++)
	{
		t+=arr[i].a;
		q.push(arr[i].a);
		if (t>arr[i].b) t-=q.top(),q.pop();
	}
	cout << q.size() << endl;
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值