反悔贪心
基本内容
反悔贪心的思想是:每次都进行操作,在以后有最优情况的时候再取消这次操作
和dp区别:不用满足最优子结构,不用满足子任务重叠.
使用标志:当发现可以贪心,但是有时候局部最优会影响全局最优.
基本操作:将选择过的放在一个堆中,对于一个新的选择,如果优于堆中的选择,那么进行替换,即反悔操作
例题:P2949 [USACO09OPEN] Work Scheduling G
一个简单的思想是用map存每个时间点最大的,然后离散化,枚举求,但是题目中要求<日期才合法&&一个日期可以有多个结束,使得这么做不可行.根据我们的经验,肯定是结束越早越优秀,我们考虑贪心,但是我们发现,并不是直接贪心就可以,所以考虑反悔贪心.
仍然先sort,考虑对于每个任务,如果能完成就贪心的完成并存进堆里,否则看一看堆中是否有价值小于它的,有的话替换
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N_ 100005
#define M_
#define P pair<ll,ll>
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin)),*p1++)
void read(ll& x)
{
char ch=gc();ll f=1;x=0;
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;
ch=gc();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-48;
ch=gc();
}
x=x*f;
}
struct node{
ll d,v;
}t[N_];
priority_queue<ll,vector<ll>,greater<ll>>q;
ll N,ans,cnt,s[N_];
int main()
{
read(N);
for(int i=1;i<=N;i++)read(t[i].d),read(t[i].v);
sort(t+1,t+1+N,[](node a,node b){
if(a.d==b.d)return a.v>b.v;
return a.d<b.d;
});
for(int i=1;i<=N;i++)
{
while(q.size()>=t[i].d&&q.top()<t[i].v)ans-=q.top(),q.pop();
if(q.size()<t[i].d)
q.push(t[i].v),ans=ans+1ll*t[i].v;
}
cout<<ans;
return 0;
}
练习
P4053 [JSOI2007] 建筑抢修和例题一样,只是堆里存花费时间
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N_ 150005
#define M_
#define P pair<ll,ll>
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin)),*p1++)
void read(ll& x)
{
char ch=gc();ll f=1;x=0;
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;
ch=gc();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-48;
ch=gc();
}
x=x*f;
}
struct node{
ll a,b;
}t[N_];
ll N,ans,sum;
priority_queue<ll> q;
bool operator < (const node a,node b)
{
if(a.b==b.b)return a.a<b.a;
return a.b<b.b;
}
int main()
{
read(N);
for(int i=1;i<=N;i++)read(t[i].a),read(t[i].b);
sort(t+1,t+1+N);
for(int i=1;i<=N;i++)
{
if(sum+t[i].a>t[i].b){
if(sum-q.top()+t[i].a<=t[i].b&&sum-q.top()+t[i].a<sum)sum=sum-q.top()+t[i].a,q.pop(),q.push(t[i].a);
}
else
sum+=t[i].a,ans++,q.push(t[i].a);
}
cout<<ans;
return 0;
}
P3545 [POI 2012] HUR-Warehouse Store,考虑能给就给,给不动,如果比前面最大顾客要的少,抢回前面顾客的,然后继续给.
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N_ 250005
#define M_
#define P pair<ll,ll>
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin)),*p1++)
#define fi first
#define se second
void read(ll& x)
{
char ch=gc();ll f=1;x=0;
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;
ch=gc();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-48;
ch=gc();
}
x=x*f;
}
struct node{
ll a,b;
}t[N_];
priority_queue<P> q;
ll N,sum;
int main()
{
read(N);
for(int i=1;i<=N;i++)read(t[i].a);
for(int i=1;i<=N;i++)read(t[i].b);
for(int i=1;i<=N;i++)
{
sum+=t[i].a;
if(sum>=t[i].b){
sum-=t[i].b;
q.push({t[i].b,i});
}
else
if(q.size()&&q.top().fi>t[i].b)sum+=q.top().fi,sum-=t[i].b,q.push({t[i].b,i}),q.pop();
}
cout<<q.size()<<'\n';
while(!q.empty())cout<<q.top().se<<" ",q.pop();
return 0;
}
P1484
一个比较有意思的反悔贪心,不在是简单的加入。
模拟一下会发现(假如都是正的)如果u选了,那么u+1,u-1不能选。那么也就是说如果我要反悔就会选u+1,u-1.但是如何反悔呢。
发现我们可以把u+1,u-1,u合并,并把u+1,u-1删去,变成v[u+1]+v[u-1]-v[u],此时如果再选这个点那么就是反悔操作.用一个双向链表即可
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N_ 300005
#define fi first
#define se second
#define P pair<ll,ll>
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin)),*p1++)
const ll inf = 1e13;
void read(ll& x)
{
char ch=gc();ll f=1;x=0;
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;
ch=gc();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-48;
ch=gc();
}
x=x*f;
}
ll tot,V[N_],pre[N_],to[N_],N,K,head,tail,v[N_],ans;
bool vis[N_];
priority_queue<P> p;
void erase(ll x)
{to[pre[x]]=to[x];pre[to[x]]=pre[x];vis[x]=1;}
int main()
{
read(N);read(K);
for(int i=1;i<=N;i++)read(V[i]),pre[i]=i-1,to[i]=i+1;
to[0]=1;pre[N+1]=N;
for(int i=to[0];i!=N+1;i=to[i])p.push({V[i],i});
tot=N+1;
ll cnt=0;
while(!p.empty()&&p.top().fi>0)
{
P u=p.top();p.pop();
if(vis[u.se])continue;
ans+=u.fi;cnt++;
if(cnt==K)break;
ll a=pre[u.se],b=to[u.se];
erase(a);erase(b);
V[u.se]=V[b]-u.fi+V[a];
p.push({V[b]-u.fi+V[a],u.se});
}
cout<<ans;
return 0;
}
注意一些细节:1.不能自定义pair的比较方式(不知道为什么,反正会错)2.弹出一个点的时候,要将V[u]更新,否则会错
小结:反悔贪心有的时候需要容斥进行

浙公网安备 33010602011771号