反悔贪心

posted on 2023-09-22 09:11:19 | under 笔记 | source

简介

当贪心策略失效时,可以考虑反悔得到全局最优解,反悔一般用堆实现。

对于一些普通反悔贪心,堆中的元素很容易想到。复杂一些的问题中,我们考虑在 朴素贪心每次确定决策 时再加入一些修正值,以此达到反悔的效果。

关键就是反悔操作的设计以及修正值取啥了,我们可以这样考虑:

\[C_{i,j}+C_{j2,k}=C_{i,k} \]

其中,\(C_{i,j}\) 表示朴素贪心策略中,决策 \((i,j)\) 的贡献值,为了得到全局最优解,即考虑到更优的决策 \((i,k)\),我们可以再将修正值 \(j2\) 加入堆中,那么满足上述式子就可视作反悔了。

(有差分那味儿了)

例题 CF865D

显然有朴素贪心策略:对于 \(a_i\),回头找到 \(\min a_j\) 然后交易。

那么会被 \(a_i<a_j<a_k\) 的情形卡掉,上述策略的答案是 \(a_j-a_i\) 而正确答案是 \(a_k-a_i\)

这就很符合反悔贪心的核心式子。

首先有 \(C(i,j)=a_j-a_i\),那么带入得:

\[a_j-a_i+k-j2=a_k-a_i \]

化简后就有 \(j2=a_j\),于是乎就解决了。

有些细节和不解还是要自己思考思考的。

#include <bits/stdc++.h>
using namespace std;

#define int long long
int n,a,ans;
priority_queue<int,vector<int>,greater<int> >q;

signed main() {
    cin>>n;
    for(int i=1; i<=n;i++){
    	scanf("%lld",&a);
    	if(!q.empty()&&q.top()<a){
    		ans+=a-q.top(); //计算贡献
    		q.pop(); 
  			 q.push(a); //反悔
    	}
    	q.push(a); 
  		//注意到ai除了被当作修正值外,自己还可以被买入,所以还要入一次队
    }
    cout<<ans;
    return 0;
}

例题 P1792 [国家集训队] 种树

朴素贪心:将所有点的价值加入大根堆,每次取最大值并标记其两边的点。

错误原因:显然,我们不一定要取这个点,有时取两边的点反而更优。

设当前点为 \(k\),那么考虑一种反悔操作,使得撤销选 \(k\) 这个操作,并计算两侧点的贡献。

易想到:重新在队列中加入一个价值为 \(a_{left}+a_{right}-a_k\) 的点便能起到反悔的效果。

不过这样做还不够严谨,也许我们不一定取 \(left,right\) 而是取 \(left_{left},right_{right}\) 呢?

注意到问题的相似性,我们便有了最终思路:对于 \(k\) 的反悔操作,是将 \(left,k,right\) 这三点合并为一点,且需要维护新点的左右点,价值即为上式。

不难发现,这样做恰好包含了所有可能情形。

程序实现中,使用双向链表即可支持上述操作。

#include<bits/stdc++.h>
using namespace std;

const int N=2e5+5;
int n,m,a[N],lt[N],rt[N],ans;
bool vis[N];
priority_queue<pair<int,int> >q;

inline void change(int u){
	vis[lt[u]]=vis[rt[u]]=true;
	a[u]=-a[u];
	if(lt[u]) a[u]+=a[lt[u]];
	if(rt[u]) a[u]+=a[rt[u]];
	lt[u]=lt[lt[u]]; rt[u]=rt[rt[u]];
	rt[lt[u]]=u; lt[rt[u]]=u;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n;i++){
		scanf("%d",&a[i]);
		q.push({a[i],i});
	}
	for(int i=1; i<=n;i++){
		lt[i]=(i-1==0?n:i-1);
		rt[i]=(i+1==n+1?1:i+1);
	}
	if(n<(m<<1)){
		cout<<"Error!";
		return 0;
	}
	while(m&&!q.empty()){
		int val=q.top().first,xb=q.top().second;
		q.pop();
		if(vis[xb]) continue;
		--m; ans+=val;
		change(xb);
		q.push({a[xb],xb});
	}
	cout<<ans;
	return 0;
}
posted @ 2026-01-14 17:57  Zwi  阅读(0)  评论(0)    收藏  举报