Loading

反悔贪心

基本内容

反悔贪心的思想是:每次都进行操作,在以后有最优情况的时候再取消这次操作
和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]更新,否则会错
小结:反悔贪心有的时候需要容斥进行

posted @ 2025-11-26 18:31  leaf_ydc  阅读(2)  评论(0)    收藏  举报