反悔贪心 学习笔记

反悔贪心


  • 反悔贪心
  • 反悔贪心的本质就是 发生新决策撤销决策
  • 使用 堆 / priority_queue 维护

反悔贪心例题:[国家集训队] 种树

最初的贪心的策略是每次选最大的可行的数
在这里插入图片描述

如果我们按照这个贪心策略,得到的答案为 100 + 1 100+1 100+1 然而实际答案是 99 + 99 99+99 99+99

我们在不改变贪心的策略的情况下,考虑反悔(即撤销之前的操作)

例如此图,我们在选完 100 100 100 的情况下,撤销 100 100 100 的操作,改选 99 + 99 99+99 99+99 (肯定不会只选一个 99 99 99

对于反悔操作,我们可以通过构造一个新的结点等于其两边的结点减去当前的结点

在这里插入图片描述

例如此图,在我们选取 a 1 a1 a1 后, a n s = 100 ans=100 ans=100 ,将 a 1 a1 a1 改成 99 + 99 − 100 = 98 99+99-100=98 99+99100=98 ,再在当前可挑选点中挑选最大的点即 a 1 ′ = 98 a_1^{'}=98 a1=98 a n s ans ans 变为 198 198 198

对于找最大值的操作,我们使用堆,而对于标记一个点左右的点我们需要使用链表

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,m,a[MAXN],pre[MAXN],nxt[MAXN],vis[MAXN];
struct Node{
	int id,val;
	bool operator < (const Node &x)const
	{
		return x.val>val;
	}
};
priority_queue <Node> q;
void init()
{
	for(int i=1;i<=n;i++) 
		pre[i]=i-1, nxt[i]=i+1;
	pre[1]=n,nxt[n]=1;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) 
	{
		cin>>a[i];
		q.push((Node){i,a[i]});
	}
	if((n>>1)<m) {puts("Error!"); return 0;}
	
	init();
	
	int res=0;
	while(m--)
	{
		while(vis[q.top().id]) q.pop();
		
		Node hd=q.top(); q.pop();
		res+=hd.val; vis[pre[hd.id]]=vis[nxt[hd.id]]=1;
		
		a[hd.id]=a[pre[hd.id]]+a[nxt[hd.id]]-a[hd.id];
		
		q.push((Node){hd.id,a[hd.id]});
		
		pre[hd.id]=pre[pre[hd.id]], nxt[hd.id]=nxt[nxt[hd.id]];
		nxt[pre[hd.id]]=hd.id, pre[nxt[hd.id]]=hd.id;
		
	}
	
	cout<<res;
	
	return 0;
}

反悔贪心例题:[APIO/CTSC2007] 数据备份

    1. 根据题意可得,如果办公楼 i i i 已经有电线连接,那么 i i i 不会再和其他办公楼相连
    1. 如下图可知,电线肯定连接相邻的两座办公楼
      在这里插入图片描述

  • 在这里插入图片描述
    例如此图,问题可以转化为求 在 1 − 2 1-2 12 2 − 3 2-3 23 3 − 4 3-4 34 4 − 5 4-5 45 号办公楼之间不相邻的线段之和最小,经典的反悔贪心问题
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
const int INF=1e9+5;
int n,k,a[MAXN],w[MAXN],vis[MAXN],pre[MAXN],nxt[MAXN];
struct Node{
	int id,val;
	bool operator < (const Node &x)const{
		return x.val<val;
	}
};
priority_queue <Node> q;
void init()
{
	w[0]=w[n]=INF; 
	for(int i=1;i<=n;i++)
		pre[i]=i-1,nxt[i]=i+1;	
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		w[i-1]=a[i]-a[i-1];
	}
	init();
	for(int i=1;i<n;i++) q.push((Node){i,w[i]});
	ll res=0;
	while(k--)
	{
		while(vis[q.top().id]) q.pop();
		
		Node hd=q.top(); q.pop();
		res+=hd.val;
		vis[pre[hd.id]]=vis[nxt[hd.id]]=1;
		
		w[hd.id]=w[pre[hd.id]]+w[nxt[hd.id]]-w[hd.id];
		q.push((Node){hd.id,w[hd.id]});
		
		pre[hd.id]=pre[pre[hd.id]], nxt[hd.id]=nxt[nxt[hd.id]];
		nxt[pre[hd.id]]=hd.id,pre[nxt[hd.id]]=hd.id;
	}
	cout<<res;
	return 0;
}

反悔贪心例题 CF730I Olympiad in Programming and Sports

题意: n n n 个人,每个人编程能力为 a i a_i ai 运动能力为 b i b_i bi p p p 个人去编程,一个人只能去一个项目,选 s s s 个人去运动,问最大能力和

我们首先确定编程队的 p p p 个人,然后将其余的人定义为 闲散人员

在确定编程队之后,运动队当前缺 s s s 个人,对于加入运动队的人员,选择两种情况的最优情况

    1. 从闲散人员中挑一个人 i i i 去运动队,贡献为 + b i +b_i +bi
    1. 从编程队挑一个人 i i i 去运动队,并挑一个闲散人员 k k k 去编程队 , 贡献为 + b i − a i + a k +b_i-a_i+a_k +biai+ak

那么我们用三个堆,分别维护 闲散人员的 a a a ,闲散人员的 b b b ,以及编程队人员的 b − a b-a ba只有已经选入编程队的人才有可能反悔

同时,我们用一个 b e l o n g belong belong 数组表记录

  • 0 0 0 闲散人员
  • 1 1 1 编程队
  • 2 2 2 运动队

注意:每次反悔操作将状态改变后要重新改变对应不同状态的堆,如:编程队员变为运动队员后进入运动队员的堆,退出变成队员的堆

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e3+5;
struct Node{
	int id,val;
	bool operator <(const Node &x)const{
		return x.val>val;
	}
}a[MAXN],b[MAXN],A[MAXN];
int n,p,s,belong[MAXN],ans;
bool cmp(Node x,Node y)
{
	return x.val>y.val;
}
priority_queue <Node> q1;	// 闲散人员的 a
priority_queue <Node> q2;	// 闲散人员的 b
priority_queue <Node> q3;	// 编程队人员的 b-a
int main()
{
	cin>>n>>p>>s;
	for(int i=1;i<=n;i++)
		cin>>a[i].val,a[i].id=i,A[i]=a[i];
		
	for(int i=1;i<=n;i++)
		cin>>b[i].val,b[i].id=i;
		
	sort(A+1,A+n+1,cmp);
	for(int i=1;i<=p;i++) 
	{
		int cur=A[i].id;
		ans+=A[i].val;
		belong[cur]=1;
		q3.push((Node){cur,b[cur].val-a[cur].val});
	} 
		
	for(int i=1;i<=n;i++)
		if(belong[i]==0) 
			q1.push(a[i]),q2.push(b[i]);
	
	while(s--)
	{
		while(!q1.empty() && belong[q1.top().id]!=0) q1.pop();
		while(!q2.empty() && belong[q2.top().id]!=0) q2.pop();
		while(!q3.empty() && belong[q3.top().id]!=1) q3.pop();

		if(q2.top().val>q3.top().val+q1.top().val)
		{
			ans+=q2.top().val;
			belong[q2.top().id]=2;
			q2.pop();
		}
		else
		{
			ans+=q3.top().val+q1.top().val;
			belong[q3.top().id]=2;
			belong[q1.top().id]=1;
			q3.pop(); 
			q3.push((Node){q1.top().id, b[q1.top().id].val-q1.top().val});
			q1.pop();
		}
	}
	
	cout<<ans<<endl;
	for(int i=1;i<=n;i++)
		if(belong[i]==1)
			cout<<i<<" ";
	puts("");
	for(int i=1;i<=n;i++)
		if(belong[i]==2) 
			cout<<i<<" ";
	return 0;
}


反悔贪心例题:CF436E Cardboard Box

题意:n 个关卡,对每个关卡,你可以花 a i a_i ai 打到一颗星,也可以花 b i b_i bi 代价得到两颗星,也可以不玩。问获得 w w w 颗星的最小代价

对于得到一颗星,有以下四种情况
在这里插入图片描述
我们需要找每次操作的最小值,用 5 个堆维护

在这里插入图片描述

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=3e5+5;

struct Node{
	int id,val;
	bool operator < (const Node &x) const{
		return x.val<val;
	}
};
int n,w,a[MAXN],b[MAXN],star[MAXN],ans;
priority_queue <Node> q1; // ai			0 to 1
priority_queue <Node> q2; // bi			0 to 2
priority_queue <Node> q3; // -ai		1 to 0
priority_queue <Node> q4; // ai-bi		2 to 1
priority_queue <Node> q5; // bi-ai		1 to 2
signed main()
{
	cin>>n>>w;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i]>>b[i];
		q1.push((Node){i,a[i]});
		q2.push((Node){i,b[i]});
	}
	while(w--)
	{
		while(!q1.empty() && star[q1.top().id]!=0) q1.pop();
		while(!q2.empty() && star[q2.top().id]!=0) q2.pop();
		while(!q3.empty() && star[q3.top().id]!=1) q3.pop();
		while(!q4.empty() && star[q4.top().id]!=2) q4.pop();
		while(!q5.empty() && star[q5.top().id]!=1) q5.pop();
		int res=1e9+7,opt=0;
		if(!q1.empty() && res>q1.top().val) res=q1.top().val,opt=1;
		if(!q5.empty() && res>q5.top().val) res=q5.top().val,opt=2;
		if(!q4.empty() && !q2.empty() && res>q4.top().val+q2.top().val) res=q4.top().val+q2.top().val,opt=3;
		if(!q3.empty() && !q2.empty() && res>q3.top().val+q2.top().val) res=q3.top().val+q2.top().val,opt=4;
		ans+=res;
		if(opt==1) // 0 to 1
		{
			star[q1.top().id]=1; 
			q5.push((Node){q1.top().id, b[q1.top().id]-a[q1.top().id]});
			q3.push((Node){q1.top().id, -a[q1.top().id]});	
			q1.pop();
		}
		if(opt==2)// 1 to 2
		{
			star[q5.top().id]=2;
			q4.push((Node){q5.top().id, a[q5.top().id]-b[q5.top().id]});
			q5.pop();
		}
		if(opt==3)// 2 to 1 & 0 to 2
		{
			star[q4.top().id]=1, star[q2.top().id]=2;
			q5.push((Node){q4.top().id, b[q4.top().id]-a[q4.top().id]});
			q3.push((Node){q4.top().id, -a[q4.top().id]});
			q4.pop();
			q4.push((Node){q2.top().id, a[q2.top().id]-b[q2.top().id]});	
			q2.pop();
		}
		if(opt==4)// 1 to 0 & 0 to 2
		{
			star[q3.top().id]=0, star[q2.top().id]=2;
			q4.push((Node){q2.top().id, a[q2.top().id]-b[q2.top().id]});
			q1.push((Node){q3.top().id, a[q3.top().id]});
			q2.pop();
			q2.push((Node){q3.top().id, b[q3.top().id]});
			q3.pop();	
		}
	}
	cout<<ans<<endl;
	for(int i=1;i<=n;i++) cout<<star[i]; 
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值