当前位置: 首页 > news >正文

[笔记] 贪心 - 3/3(反悔贪心)

我们知道,贪心从来都是只顾现在,不太考虑将来的算法,然而,这种鼠目寸光的策略,在很多题目中并不成立,因为贪心算法只会紧紧抱住眼前的利益不放,而忽视了将来可能有更大的利益在等着它。

于是反悔贪心这一种特殊的贪心就出现了,它同样是贪心,但比普通的贪心更灵活,它学会了在合适的时机放下曾经拿起的东西,从而获得更大的利益。

当然,反悔贪心的本质还是贪心,所以反悔策略不是万能的,也会有考虑不全的情况,这时就得放弃贪心转而考虑其他算法。

P2107 小 Z 的 AK 计划

题意很简单,我们先看一下绝大多数题解是怎么讲这个题的:

  • 考虑反悔贪心。我们尽可能 AK 所有路过的机房,如果 AK 当前机房后时间不够用,则从所有 AK 过的机房(包括当前)中取耗时最长的删掉,直到时间够用或者没有机房为止。 ——引用自 Sinktank的题解

非常简洁明了的把思路和代码的大体框架讲明白了,代码大致如下(先别写):

常规写法
#include<bits/stdc++.h>
#define int long long
using namespace std;const int N=1e5+10;
struct Node{int x,t;
}a[N];
int n,m,ans;bool cmp(Node x, Node y){return x.x<y.x;
}
signed main(){cin.tie(0)->sync_with_stdio(0);cin>>n>>m;for(int i=1; i<=n; i++) cin>>a[i].x>>a[i].t;stable_sort(a+1, a+n+1, cmp);priority_queue<int> q;int lst=0;for(int i=1; i<=n; i++){int tim=a[i].x-lst+a[i].t;if(m<tim){while(!q.empty() && m<tim){// 为什么用while?int mx=q.top();if(mx<=a[i].t) break;m+=mx;q.pop();}}if(m>=tim){q.push(a[i].t);lst=a[i].x;m-=tim;}ans=max(ans, (int)q.size());}cout<<ans;return 0;
}

代码整体结构和前面的题区别不大,设当前考虑第 \(i\) 个机房,如果时间不够用,就反悔往外弹,直到时间够用了就把第 \(i\) 个加进堆里。

唯一的细节是,这个代码的反悔用了 while,而不是 if。

这就使人摸不着头脑了。用 while,意味着要一次性弹出多个元素,然而,如果弹出了多个元素,那么就算把第 \(i\) 个加回来,总数量还是净减少了。

虽然这样给后面的元素留下了更多机会,但从数量上看,似乎并不是最优解,为什么这样的贪心策略是正确的?

对于这一问题,大部分题解并没有给出明确解答,就算是更深入的题解,也没有解释清楚。所以在这里,会给出一个较为详细的解释。

先说结论,事实上,while 反悔也只反悔掉一个,做的事情和 if 没有区别。

这是为什么呢?想要证明贪心策略正确性,要么证明把反悔掉的加回来不会变优,要么证明在反悔之前的状态的答案,不会在继续往后扫描的过程中被更新。

这里使用后者进行说明。首先,进行反悔的条件是,当前时间不够用,并且堆顶的时间比 \(t_i\) 大,如果第二条不满足,那么把第 \(i\) 个加进去还不如不加,也不需要反悔。

因此,如果我们走到了 \(x_i\) 的位置要反悔,因为堆顶一定比 \(t_i\) 大,反悔一次时间肯定就够了。但代码中的 while 又是必需的,如果改成 if 会 wa,也就是说必须反悔多次,这似乎形成了矛盾。

但矛盾是不可能出现的,这只能说明,多出来的那些反悔次数,用在了别的环节,也就是上句话中我们最容易忽视的地方:走到 \(x_i\) 的位置

是的,问题就这样迎刃而解了,我们忽略了剩下的时间可能甚至不够走到 \(x_i\) 的位置,如果真是这样,那么,\(x_i\) 往后的位置就都走不到了,这个答案也就不会继续扩展了,满足第二条证明方法。

这时要是还想走到 \(x_i\),只能记录下当前答案,然后扔掉一些比较大的元素释放时间,直到足够走到为止。

现在能走到了,再考虑第 \(i\) 个要不要放进去,要不要反悔。

再仔细分析代码,我们发现,这个 while 循环实现的就是上面的过程,只不过把为了走到 \(x_i\) 释放时间的过程和对第 \(i\) 个元素做反悔的过程合在一起了,所以会很难理解,完全按上面的思路去写是这样的:

好理解的写法
#include<bits/stdc++.h>
#define int long long
using namespace std;const int N=1e5+10;
struct Node{int x,t;}a[N];
int n,m,ans;bool cmp(Node x, Node y){return x.x<y.x;}
signed main(){cin.tie(0)->sync_with_stdio(0);cin>>n>>m;for(int i=1; i<=n; i++) cin>>a[i].x>>a[i].t;priority_queue<int> q;stable_sort(a+1, a+n+1, cmp);for(int i=1; i<=n; i++){int tim=a[i].x-a[i-1].x;while(!q.empty() && m<tim){m+=q.top(), q.pop();}m-=tim;if(m<a[i].t && !q.empty() && q.top()>=a[i].t)m+=q.top(), q.pop();if(m>=a[i].t){q.push(a[i].t);m-=a[i].t;}ans=max(ans, (int)q.size());}cout<<ans;return 0;
}
http://www.jsqmd.com/news/1270181/

相关文章:

  • 航空业地下航线交易系统:从刚性排班到弹性资源配置
  • 爬虫转大模型:Demo跑通就敢上线?权限与日志才是生死线
  • 2026年7月河北省廊坊市移动300M融合宽带办理避坑实录 - 找卡家园
  • 【前端性能】高性能滚动 scroll 及页面渲染优化
  • 【AI视频教育黄金公式】:20年教研专家亲授3大底层逻辑,90%教师不知道的5分钟爆款课件生成法
  • 实测!MiniCPM-o-2_6-GPTQ与GPT-4o/Claude 3.5性能对比:8B参数如何超越巨头模型?
  • Gowid生态系统:探索丰富的第三方扩展与工具
  • 2026长沙AIGC校企合作实训基地全景梳理:5家主流机构产教融合实力深度对比 - 互联网科技品牌测评
  • 构建离线优先应用:wasm-service的ServiceWorker缓存策略详解
  • 2026年7月浙江省宁波市电信200M融合宽带避坑攻略 - 找卡家园
  • 开源AI代理Hermes 0.8核心架构与生产实践
  • 宏智树AI论文写作工具实测:智能选题到格式自动化的全流程解决方案
  • 纽顺阀门集团有限公司-闸阀/电动闸阀/电动蝶阀/对夹蝶阀/不锈钢蝶阀/气动球阀/截止阀/止回阀/调节阀:2026实力阀门厂家 - 企业推荐官【官方】
  • License-Plate-Recognition实战:从安装到运行的完整步骤(附代码注释)
  • tg-signer高级技巧:定时任务+随机误差,让自动签到更隐蔽
  • C++网络聊天室实战:从Socket到epoll,掌握高并发服务器核心架构
  • 嵌入式实时系统原子操作与缓冲池:DSP/BIOS并发与内存管理实战
  • 2026深圳福田区搬迁公司综合实力测评:企业整体搬迁方案优选指南 - szxybj
  • OMAP4470与OMAP4460芯片差异解析:从Mailbox到调试支持的实战迁移指南
  • 【Bug已解决】[Bug]: vllm 0.22 nccl error: invalid usage 解决方案
  • Logging Made Easy GPO配置完全手册:从导入到链接的5个关键步骤
  • 2026年7月河北省廊坊市移动600M融合宽带安装流程 - 找卡家园
  • AI协作规则:提升团队效能的底层逻辑与实践
  • 2010-2023年城市间劳动力市场分割程度指数数据
  • Android Studio Poet:终极Android大型项目生成工具,一键模拟真实开发环境
  • 深入解析μDMA控制器:中断机制、寄存器配置与AES加密实战
  • 2026武汉汽车改装哪家专业?恒信飞达硬实力解析,蔚来ES9升级5D航空铝地板案例实录 - 前沿观察站
  • HarmonyOS开发实战:笔友-应用发布 Checklist——签名、隐私政策、权限声明、上架审核要点
  • 2026年7月河北省廊坊市移动1000M融合宽带怎么安装? - 找卡家园
  • Ministral-3-8B-Base-2512-bf16模型家族全解析:Base/Instruct/Reasoning版本区别与应用场景