反悔贪心
目录:
- 个人理解
- 反悔贪心的分类
- 反悔自动机
- 反悔堆
- 例题简析及代码
一、个人理解:
贪心本身是没有反悔操作的,贪心求的就是当前的最优解。但当前的最优解有可能是局部最优解,而不是全局最优解,这时候就要进行反悔操作。
反悔操作指的是这一步的贪心不是全局最优解,我们就退回去一步(人工或自动判断),换一种贪心策略。按照判断方式的不同可以分为反悔自动机和反悔堆两种方法。
二、反悔贪心的分类:
反悔自动机:
即设计一种反悔策略,使得随便一种贪心策略都可以得到正解。
基本的设计思路是:每次选择直观上最接近全局最优解的贪心策略,若发现最优解不对,就想办法自动支持反悔策略。(这就是自动机的意思)
具体题目具体分析。
反悔堆:
即通过堆(大根堆、小根堆)来维护当前贪心策略的最优解,若发现最优解不对,就退回上一步,更新最优解。
由于堆的性质,使得堆的首数据一定是最优的,这就可以实现快速更新最优解。
三、例题简析及代码
USACO09OPEN 工作调度Work Scheduling (反悔堆)
Description:
有 \(n\) 项工作,每 \(i\) 项工作有一个截止时间 \(D_i\) ,完成每项工作可以得到利润 \(P_i\) ,求最大可以得到多少利润。
Method:
做这道题的时候并没有想到反悔贪心,只是想到一个错误的贪心算法。按照截止时间为第一关键字,利润为第二关键字排序,统计一遍即可。
显然上面的贪心算法刻印被Hack掉。可以先不选择当前截止时间的利润,等一下选择下一个更大的利润,这样可以得到更大的最优解。
但我们发现这个贪心策略错误的原因是当前的最优解可能不是全局最优解,显然符合反悔贪心的思想。于是我们用一个反悔堆维护最优解。
假如满足题设条件(即没有超出截止时间)就分成两种情况:若当前的最优解比原来的最优解(堆顶)更优秀,我们就更新全局最优解,把原来的最优解丢出去,再把当前的最优解放进去(即反悔策略);反之,就不管了。假如不满足特设条件,就把当前的最优解丢进堆里,更新全局最优解即可。
Code:
#include<bits/stdc++.h> #define int long long #define Maxn 100010 inline void read(int &x) { int f=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=f; } using namespace std; int n; struct node { int D,P; bool operator <(const node &x)const { return D<x.D; } }job[Maxn]; priority_queue<int,vector<int>,greater<int> >qu; signed main() { // freopen("Job.in","r",stdin); // freopen("Job.out","w",stdout); read(n); for(int i=1;i<=n;i++) { read(job[i].D),read(job[i].P); } sort(job+1,job+n+1); int ans=0; for(int i=1;i<=n;i++) { if(qu.size()>=job[i].D)//符合条件 { if(qu.top()<job[i].P)//当前的最优解比原来的最优解(堆顶)更优秀 { ans-=qu.top();//更新全局最优解 qu.pop();//把原来的最优解丢出去 qu.push(job[i].P);//把当前的最优解放进去 ans+=job[i].P;//更新全局最优解 } }else//不符合条件 { qu.push(job[i].P);//把当前的最优解丢进堆里 ans+=job[i].P;//更新全局最优解 } } printf("%lld",ans); return 0; }
咕咕咕……
咕咕咕……