学习笔记 反悔贪心

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.mzph.cn/news/732557.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA循环中标记的作用

在Java循环中标记的作用是为循环语句提供一个标识符&#xff0c;使得程序可以在循环嵌套时跳出指定的循环。它可以用于在内部循环中控制外部循环&#xff0c;或者在多个嵌套循环中控制跳出特定的循环块。 标记通常与break和continue语句一起使用。使用break语句配合标记可以跳…

CentOS上安装与配置Nginx

CentOS上安装与配置Nginx Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;并在一个BSD-like协议下发行。以下是在CentOS系统上安装和配置Nginx的步骤。 &#x1f31f; 前言 欢迎来到我的小天地&#xff0c;这…

在深度学习中,时间、空间、通道三个维度是什么?

在深度学习中&#xff0c;时间、空间、通道三个维度是什么&#xff1f; 在深度学习中&#xff0c;时间、空间和通道是描述输入数据的三个主要维度。 空间维度&#xff08;Spatial Dimension&#xff09;&#xff1a; 指的是输入数据在空间中的排列方式。对于图像数据来说&…

Web Servlet

目录 1 简介2 创建Servlet项目并成功发布运行3 新加Servlet步骤4 Servlet项目练习5 Servlet运行原理6 操作 HTTP Request头的方法(部分方法示例)7 操作 HTTP Response头的方法(部分方法示例)8 两种重定向(页面跳转)方法9 Cookie9.1 Cookie工作原理9.2 cookie构成9.3 Servlet 操…

Java并发包中的ConcurrentLinkedQueue与LinkedBlockingQueue深度对比

Java并发包中的ConcurrentLinkedQueue与LinkedBlockingQueue深度对比 在Java的并发编程中&#xff0c;队列是一种非常重要的数据结构&#xff0c;它们提供了线程安全的数据共享方式。java.util.concurrent包中提供了多种并发队列&#xff0c;其中ConcurrentLinkedQueue和Linke…

c++中的lambda表达式

简介 & 用法 lambda表达式是c11引入的一个重要特性&#xff0c;基本语法如下 [捕获列表](形参列表) -> 返回类型 {// 函数体 }其中捕获列表和形参列表可以为空&#xff0c;返回值类型大部分情况下可以忽略不写。 lambda表达式的结构整体上和普通函数一样&#xff0c;特…

docker study

一些基本命令 查看构建的镜像列表&#xff1a; 使用以下命令查看已经构建的 Docker 镜像&#xff1a; docker images这将显示你本地计算机上的所有 Docker 镜像&#xff0c;找到你刚刚构建的镜像并记下它的名称和标签。 运行 Docker 容器&#xff1a; 使用以下命令运行 Docker…

力扣题库第6题:三数之和

题目内容&#xff1a; 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重…

axios的详细使用

目录 axios&#xff1a;现代前端开发的HTTP客户端王者 一、axios简介 二、axios的基本用法 1. 安装axios 2. 发起GET请求 3. 发起POST请求 三、axios的高级特性 1. 拦截器 2. 取消请求 3. 自动转换JSON数据 四、axios在前端开发中的应用 五、总结 axios&#xff1a…

【JS】判断是否安装了某个Chrome插件

前提 manifest.json 清单 下文均以manifest.json v3介绍。 因为Chrome官方文档中明确说明&#xff0c;v2已经弃用了。 ID 由于浏览器的安全策略&#xff0c;以下方法均在「已知扩展程序 ID」 的前提下才可实现。 获取扩展程序ID 进入扩展程序管理页&#xff0c;找到对应插…

Python基本数据类型之散列类型详解

前言&#xff1a; python的基本数据类型可以分为三类&#xff1a;数值类型、序列类型、散列类型&#xff0c;本文主要介绍散列类型。 一、散列类型 散列类型&#xff1a;内部元素无序&#xff0c;不能通过下标取值 1&#xff09;字典&#xff08;dict&#xff09;&#xff…

vscode中使用nvm安装node及创建vue3项目

使用vscode创建vue3项目 1。安装nvm Releases coreybutler/nvm-windows (github.com) 打开下载nvm.exe并安装 2。安装node.js 用管理员身份打开vscode&#xff0c;新建终端选择git bash&#xff0c;运行nvm list available选择lts版本&#xff0c;比如&#xff1a;16.16.…

【DIY】电子制作创意作品:有趣的激光竖琴

在上海世博会的伊朗馆&#xff0c;我看到了一架没有琴弦的竖琴&#xff0c;那是众多参观者公认的伊朗馆里最有趣的展品&#xff01;参观者只要伸手穿过那架通体黑色的竖琴&#xff0c;音调就会被“奏响”。没有琴弦怎么奏响&#xff1f;工作人员为我们揭示了秘密——他按了一下…

Spring Boot搭建入门

Spring Boot简介 Spring Boot是对Spring进行的高度封装&#xff0c;是对Spring应用开发的高度简化版&#xff0c;是Spring技术栈的综合整合&#xff0c;是J2EE的一站式解决方案。想要精通Spring Boot的前提是需要熟悉Spring整套技术栈原理与内容。 Spring Boot的优点&#xf…

背景虚拟化组件,透明模糊

问题当我们背景想要进行透明或者模糊处理的时候我们一般我们可以以通过 rgba 的第四个位置可以进行透明处理&#xff0c;但是模糊不行 需要懂得知识点&#xff0c;定位&#xff0c;属性加强&#xff0c;结构化&#xff0c;react 插槽 话不多说上代码 子组件 import logincs…

RN的父传子和子传父及方法调用(函数式组件)

在React Native中&#xff0c;父组件向子组件传递数据通常通过props实现&#xff0c;而子组件向父组件传递数据则通常通过回调函数实现。下面是一个简单的示例&#xff0c;演示了父组件向子组件传递数据和子组件向父组件传递数据的方法&#xff1a; 父传子 父组件 // ParentC…

指针篇章-(冒泡排序详解)

冒泡排序 图解 tmp图解 内容图解 每次循环的次数减少 for循环详解 冒泡排序是一种简单的排序算法&#xff0c;它重复地遍历要排序的数列&#xff0c; 一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。 遍历数列的工作是重复地进行直到没有再需要交换&…

Double和Float类

Double类 功能&#xff1a;实现对Double基本型数据的类包 构造方法&#xff1a; (double num) double Value()方法&#xff1a;返回对象中的double型数据。 Float类 功能&#xff1a;实现对float基本型数据的类包装。 构造方法&#xff1a; (float num) Float Value()方法…

云计算项目九:K8S安装

K8S安装 Kube-master安装 按照如下配置准备云主机 防火墙相关配置&#xff1a;禁用selinux&#xff0c;禁用swap&#xff0c;且在firewalld-*。上传kubernetes.zip 到跳板机 配置yum仓库&#xff08;跳板机&#xff09; 跳板机主机配置k8s软件源服务端 [rootjs ~]# yum -y…

设计模式-行为型模式-备忘录模式

备忘录&#xff08;Memento&#xff09;&#xff1a;在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。[DP] //首先&#xff0c;我们定义Originator类&#xff0c;它有一个状态和…