反悔贪心 学习笔记

什么是反悔贪心

首先,贪心本身没有反悔操作,贪心求的是当前的最优解。但当前的最优解有可能是局部的最优解,而不是全局的最优解,这时候,我们就需要用反悔贪心。如下面的图。如果不进行反悔操作,一直朝着当前局面的最优解走,那么最后很有可能就被困在局部最优解,而不是全局最优解。就像下图中爬山爬到了一个小山顶,但并不是最高的山顶。
在这里插入图片描述
但是反悔贪心允许我们在发现现在不是全局最优解的情况下,回退一步或者若干步,去采取另外的策略得到全局最优解。就像站在山峰上,看到了有更高的山峰,那么我们就会先下山,然后去爬那个更高的山峰。
在这里插入图片描述
总的来说,反悔操作值的是这一步的贪心不是全局最优解,我们就退回去一步, ( ( (人工或者自动判断 ) ) ),换一种贪心策略,去找全局最优解。一般有两种,反悔堆反悔自动机

  1. 反悔堆:通过 ( ( (大根堆,小根堆 ) ) )来维护当前贪心策略的最优解,若发现最优解不对,就退回上一步,更新最优解。由于堆的性质,使得堆的首数据一定是最优的,这样就可以快速更新最优解。
  2. 反悔自动机:即设计一种反悔策略,使得随便一种贪心策略都可以得到正解。基本的思路是,每次选择直观上最接近全局最优解的贪心策略,如果发现最优解不对,就想办法自动支持反悔策略。一般需要反悔自动机的题都是通过差值巧妙达到反悔的目的。

例题

1. 1. 1.反悔堆

洛谷 P2949 [USACO09OPEN]Work Scheduling G
在这里插入图片描述
首先我们可以想到一个贪心,先完成比较紧急的任务,就是 d i d_i di 靠前的,但很明显,如果后面的 p i p_i pi 更大,需要在前面完成,那么这个贪心策略就是错的。

我们考虑如何进行反悔贪心。我们先将工作按截至时间从小到大排序,然后开一个小根堆,首先,如果当前的时间还没满,就是说在当前时间可以工作,那么我们就将这个工作的利润加进 a n s ans ans,同时把这个利润加入小根堆。否则,当此时时间已经满了,也就是说做了的任务数量 ≥ d i \geq d_i di 了,我们就要进行反悔贪心操作。由于堆顶的利润是最小的,所以我们可以将堆顶的利润减掉,然后把当前的利润加进来,这样,既可以保证工作的数量在限制里面,而且也能让答案变得更大。这就是最基础的通过反悔堆实现的反悔贪心。

#include <bits/stdc++.h>
#define re register
#define int long long 
#define rep(a,b,c)  for(re int a(b) ; a<=c ; ++a)
#define drep(a,b,c) for(re int a(b) ; a>=c ; --a)
using namespace std;
inline int read(){
   
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
   if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){
   x=(x<<1)+(x<<3)+(ch^48) ; ch=getchar();}
	return x*f;
}
typedef pair<int,int> pii;
const int M = 1e5+10;
int n,ans;
pii a[M];
signed main(){
   
	n = read();
	rep(i,1,n) a[i].first = read(),a[i].second = read();
	sort(a+1,a+n+1);
	priority_queue<int,vector<int>,greater<int> >q;
	rep(i,1,n){
   
		if(a[i].first > q.size()) q.push(a[i].second),ans += a[i].second;
		else{
   
            if(a[i].second > q.top()){
   
    			ans -= q.top();
    			ans += a[i].second;
    			q.pop();
    			q.push(a[i].second);
            }
		}
	}
	printf("%lld\n",ans);
	return 0;
}

洛谷 P4053 [JSOI2007] 建筑抢修
在这里插入图片描述
跟上面那道题类似,也是通过反悔堆来进行反悔贪心。由于我们要尽量让修的建筑修的时间短,所以我们开一个大根堆。先按 T 2 T2 T2 从小到大排序,也就是可以理解为上一道题的截止时间。然后我们要记录修我们修过的建筑一共花了多长时间。如果加上这个建筑修的时间 ≤ \leq 当前建筑的截止时间,那么这个建筑就可以直接修,把一共修的时间加上这个建筑修需要的时间,同时将这个建筑修需要的时间加入大根堆,并 a n s + 1 ans+1 ans+1。否则,当加上这个建筑修需要的时间已经超过截止时间了,那么我们就要跟堆顶的那个建筑修所需要的时间比较,如果比那个小,那么那个建筑就不修,修当前这个建筑,同时更新一共修的建筑的总共时间。这样我们就完成了反悔贪心。

#include <bits/stdc++.h>
#define re register
#define int long long 
#define rep(a,b,c)  for(re int a(b) ; a<=c ; ++a)
#define drep(a,b,c) for(re int a(b) ; a>=c ;
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值