学习笔记 反悔贪心

2024-03-10 16:36
文章标签 学习 笔记 贪心 反悔

本文主要是介绍学习笔记 反悔贪心,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0.写在前面

好久没更了,这周是开学第一周 A C M ACM ACM队临时安排讲课任务,没人讲,我就揽下来这活了。前两天有一道 c f cf cf d i v 2 C div2C div2C用到了反悔贪心这个技巧,也不需要什么前置算法就可以学,所以我第一时间想到的就是讲反悔贪心了,顺便更一下好久没更过的博客当备课了。

这个技巧的思维含量不是很高,大家可能或多或少在之前无意间用出来过。我第一次了解这个技巧是在去年暑假的时候,我的队友跟我说要给我讲一个他新学的小 t r i c k trick trick,给我看了一道题洛谷P1484 种树,我之前从来没了解过这个小技巧但是做过这个题。

1.随便讲讲

字面意思,反悔贪心就是可以反悔的贪心。贪心本身是没有反悔操作的,通过对一个问题应用特定的问题选出最优解。然而有时候我们发现贪心只能得到局部最优解,这时候可以进行反悔操作。为了实现反悔操作,我们要记录下前面操作的最大/最小值等信息,因此反悔贪心大多会使用一些数据结构进行实现。

毕竟这只是一个小技巧不是一个算法,只靠说大家可能不好理解,我们直接进入例题,就题分析。

2.例题

CF865D Buy Low Sell High

一个我觉得比较好解释反悔贪心思想的例题

题意:对于一支股票,你可以知道后面 N N N天这个股票的价格,每天可以在两种操作中选一种:买入一股该股票、卖出一股之前买入的股票。要求在最后一天手里不能有任何股票。求最后最多能赚多少钱。其中 N ≤ 3 × 1 0 5 N\le 3\times 10^5 N3×105

对于买股票的操作,我们观察到题目要求最后一天手里不能有任何股票,所以我们只要没有卖出的操作,就直接进行买入操作,如果最后这支股票卖不出去,我们进行反悔,这股票我不买了退了,就当我那一天没买过。

对于卖股票的操作,我们什么时机卖呢,肯定是价格高于当前买入,但是如果卖亏了怎么办,我们选择反悔上次卖的,相当于上次不卖了退回来,这次再卖。

这就是反悔贪心的思想,具体怎么实现呢?

我们使用小根堆 q q q来存储买入的价格。

对于第 i i i天价格 a i a_i ai,如果该价格不超过当前小根堆堆顶的价格,那么我们无法进行卖出操作,只能进行买入操作,因此将 a i a_i ai入堆即可。此时入堆的 a i a_i ai的意义为:记录下当前的买入价格,为后续卖出操作准备。

如果该价格超过了当前小根堆堆顶的价格,即 a i > q . t o p a_i>q.top ai>q.top,意味着我们可以进行卖出操作,此时我们就将堆顶元素弹出,并将 a i a_i ai入堆,该操作堆答案的贡献为 a i − q . t o p a_i-q.top aiq.top。此时入堆的 a i a_i ai的意义为:记录当前卖出的价格,为后续反悔操作准备。此时我们会发现,如果后续进行了反悔操作,那么就相当于这一天没有卖出,因此还要将这一天的价格再次入堆,此时入堆的 a i a_i ai的意义为:填补进行了反悔操作后这一天的空挡。

这样我们就通过用一个小根堆来维护了我们反悔贪心的思想。

代码很短↓

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ans;
int n;
priority_queue<int,vector<int>,greater<int>> q;
int main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin>>n;for(int i=1,x;i<=n;i++){cin>>x;if(!q.empty()&&q.top()<x)ans+=x-q.top(),q.pop(),q.push(x);q.push(x);}cout<<ans<<'\n';
}
洛谷P1484 种树

一道非常经典的反悔贪心题

题意:给定一个长度为 n ( n ≤ 3 × 1 0 5 ) n(n\le3\times 10^5) n(n3×105)的序列 a a a,第 i i i个数为 a i ( − 1 0 6 ≤ a i ≤ 1 0 6 ) a_i(-10^6\le a_i\le 10^6) ai(106ai106),求在该数列中选出不超过 k ( 0 ≤ k ≤ ⌊ n 2 ⌋ ) k(0\le k\le\lfloor\frac{n}{2}\rfloor) k(0k2n⌋)个数的和最大。限制条件为选了第 i i i个位置的 a i a_i ai就不能选该位置两侧的 a i − 1 a_{i-1} ai1 a i + 1 a_{i+1} ai+1

对问题进行简化:取消限制条件,即在 n n n个数中选出不超过 k k k个数的和最大。

利用贪心的思想,从最大的数开始选,直到选满 k k k个数或选到负数为止。

现在回到这个问题,如果继续利用之前的贪心的思想,很容易举出反例。例如 a [ ] = { 2 , 3 , 2 } a[]=\{2,3,2\} a[]={2,3,2},从最大的开始选会选到 a 2 = 3 a_2=3 a2=3,但是最有策略明显是选择 a 1 a_1 a1 a 3 a_3 a3,贡献为 a 1 + a 3 = 4 > 3 = a 2 a_1+a_3=4>3=a_2 a1+a3=4>3=a2。通过上述例子,我们发现选当前位置的两侧和选当前位置 i i i的差值 d = a i + 1 + a i − 1 − a i d=a_{i+1}+a_{i-1}-a_i d=ai+1+ai1ai。如果只考虑这三个位置,当 d > 0 d>0 d>0时,我们选择两侧的更优;当 d < 0 d<0 d<0时,我们选择第 i i i个位置更优。

因此考虑反悔贪心,我们每次选到 a i a_i ai的时候,将 a i a_i ai记录进答案,同时将当前位置和两侧的差值 d d d放回 a i a_i ai,并将 a i − 1 a_{i-1} ai1 a i + 1 a_{i+1} ai+1拿出序列。这样如果我们再次选到了 a i a_i ai,这时第 i i i个位置对答案的贡献为第一次的 a i a_i ai和第二次的 d = a i + 1 + a i − 1 − a i d=a_{i+1}+a_{i-1}-a_i d=ai+1+ai1ai,总贡献为 a i + 1 + a i − 1 a_{i+1}+a{i-1} ai+1+ai1,其意义是不选择 a i a_i ai选择 a i + 1 a_{i+1} ai+1 a i − 1 a_{i-1} ai1

因此只需要开一个大根堆,代表待选的数的集合,维护一个 l , r l,r l,r数组代表当前位置的左侧和右侧,同时记录已经被选过的位置防止选重即可。时间复杂度为 O ( k log ⁡ n ) O(k\log n) O(klogn)

代码↓

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 100;
struct node{int id,w;node(){id=w=0;}node(int x, int y){id=x,w=y;}bool operator<(const node &x) const{return w<x.w;}
};
int n,k,a[N],l[N],r[N];
bool vis[N];
priority_queue<node> q;
typedef long long ll;
ll ans;
int main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin>>n>>k;for(int i=1;i<=n;i++){cin>>a[i];l[i]=i-1,r[i]=i+1;q.push(node(i,a[i]));}for(int i=1;i<=k;i++){while(vis[q.top().id])q.pop();node now=q.top();if(now.w<0)break;q.pop();ans+=now.w;a[now.id]=now.w=a[l[now.id]]+a[r[now.id]]-a[now.id];q.push(now);vis[l[now.id]]=vis[r[now.id]]=1;l[now.id]=l[l[now.id]],r[now.id]=r[r[now.id]];l[r[now.id]]=r[l[now.id]]=now.id;}cout<<ans<<'\n';}
cf1935C Messenger in MAC

最新的用到反悔贪心的cf比赛题

题意:有 n ( 1 ≤ n ≤ 2000 ) n(1\le n\le 2000) n(1n2000)条短信,第 i i i条短信包含两个信息 a i a_i ai b i ( 1 ≤ a i , b i ≤ 1 0 9 ) b_i(1\le a_i,b_i\le 10^9) bi(1ai,bi109),读取下标为 p 1 , p 2 , … , p k p_1,p_2,\dots,p_k p1,p2,,pk的短信所需要的时间为 ∑ i = 1 k a p k + ∑ i = 1 k − 1 ∣ b p k − b p k + 1 ∣ \sum_{i=1}^{k}a_{p_k}+\sum_{i=1}^{k-1}\mid b_{p_k}-b_{p_{k+1}} \mid i=1kapk+i=1k1bpkbpk+1。求在给定时间 l ( 1 ≤ l ≤ 1 0 9 ) l(1\le l\le 10^9) l(1l109)内最多能读取多少条短信。 T ( 1 ≤ T ≤ 5 × 1 0 4 ) T(1\le T\le 5\times10^4) T(1T5×104)组数据, ∑ n 2 ≤ 4 × 1 0 6 \sum n^2\le 4\times10^6 n24×106

对问题进行拆分,分成两个问题:选出第 p 1 , p 2 , … , p k p_1,p_2,\dots,p_k p1,p2,,pk位置的短信,如何安排能使得总时间最短;以及如何选出 p 1 , p 2 , … , p k p_1,p_2,\dots,p_k p1,p2,,pk

首先第一个问题:选出第 p 1 , p 2 , … , p k p_1,p_2,\dots,p_k p1,p2,,pk位置的短信,如何安排顺序能使得总时间最短?

观察式子,可以发现对于信息 a a a,如何安排对时间的贡献是相同的,因此只需要考虑信息 b b b,不难发现将 b b b按顺序排列是最优的,此时所需时间为 ∑ i = 1 k a p k + b m a x − b m i n \sum_{i=1}^k a_{p_k}+b_{max}-b_{min} i=1kapk+bmaxbmin。即对于选出的第 p 1 , p 2 , … , p k p_1,p_2,\dots,p_k p1,p2,,pk位置的短信,我们读取这些短信所需时间为所有短信 a a a的总和加上这些短信的 b b b中最大与最小的差。

对于第二个问题,我们考虑到上面化简的式子中,如果确定了 b b b的值,那么就是选出最小的 a i a_i ai,因此我们考虑按 b b b从小到大排序, b b b相同的按 a a a从小到大排序,并枚举最小的 b b b。我们的贪心策略是使得 b m a x − b m i n b_{max}-b_{min} bmaxbmin尽可能的小,因此先将 b b b相同的选出,再去考虑 b b b不同的。对于一个 b b b大于当前 b b b的短信,读取它的时间为 a i + b − b n o w a_i+b-b_{now} ai+bbnow。此时在考虑反悔操作,如果前面选了 a p > a i + b − b n o w a_p>a_i+b-b_{now} ap>ai+bbnow,就将 a p a_p ap反悔,不进行选择,并将当前的 b b b进行更新。通过建一个堆即可维护反悔操作。

赛时代码↓

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N = 3e5 + 100;
const int mod = 998244353;
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
struct node{int a,b;node(){a=b=0;}node(int x, int y){a=x,b=y;}
}ms[N];
bool cmp(node x, node y){if(x.b!=y.b)return x.b<y.b;else return x.a<y.a;
}
int n,T,nxt[N];
ll l;
void solve(){cin>>n>>l;for(int i=1;i<=n;i++)cin>>ms[i].a>>ms[i].b;sort(ms+1,ms+1+n,cmp);ms[n+1]=node(0,0);nxt[n]=n+1;for(int i=n-1;i;i--){nxt[i]=nxt[i+1];if(ms[i].b!=ms[i+1].b)nxt[i]=i+1;}int ans=0;for(int i=1;i<=n;i++){ll now=ms[i].a;int nb=ms[i].b,cnt=1;if(now>l)continue;priority_queue<int> q;for(int j=i+1;j<nxt[i];j++){if(now+ms[j].a<=l)now+=ms[j].a,q.push(ms[j].a),cnt++;else break;}ans=max(ans,cnt);for(int j=nxt[i];j<=n;j++){if(ms[j].b!=nb){now+=ms[j].b-nb;nb=ms[j].b;while(now>l&&!q.empty()){now-=q.top();q.pop();cnt--;}if(now>l)break;}if(now+ms[j].a<=l)now+=ms[j].a,q.push(ms[j].a),cnt++;else {if(!q.empty()){if(ms[j].a<q.top()){now+=ms[j].a;now-=q.top();q.pop();q.push(ms[j].a);}}}ans=max(ans,cnt);}}cout<<ans<<'\n';
}
int main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin>>T;while(T--)solve();return 0;
}

这篇关于学习笔记 反悔贪心的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/794837

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

usaco 1.3 Barn Repair(贪心)

思路:用上M块木板时有 M-1 个间隙。目标是让总间隙最大。将相邻两个有牛的牛棚之间间隔的牛棚数排序,选取最大的M-1个作为间隙,其余地方用木板盖住。 做法: 1.若,板(M) 的数目大于或等于 牛棚中有牛的数目(C),则 目测 给每个牛牛发一个板就为最小的需求~ 2.否则,先对 牛牛们的门牌号排序,然后 用一个数组 blank[ ] 记录两门牌号之间的距离,然后 用数组 an

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

poj 3190 优先队列+贪心

题意: 有n头牛,分别给他们挤奶的时间。 然后每头牛挤奶的时候都要在一个stall里面,并且每个stall每次只能占用一头牛。 问最少需要多少个stall,并输出每头牛所在的stall。 e.g 样例: INPUT: 51 102 43 65 84 7 OUTPUT: 412324 HINT: Explanation of the s