反悔贪心 & 模拟费用流
反悔贪心 & 模拟费用流
参考资料来源 cyt
前言
很多找到一种可行的方案,匹配(选择)某些东西,使价值最优化
的问题可以建出费用流模型。
但是直接跑费用流的复杂度是不对的。
我们又想到可以用简单的贪心思路解决这些问题,然而一般的贪心都假掉了。
于是我们考虑模拟费用流的退流操作来做贪心,这就是反悔贪心,其实也是在模拟费用流的各种操作。
所以说反悔贪心(可撤销贪心)和模拟费用流是一个东西。
不过对于一道题目,我们可能会从费用流建模的角度入手。也可能从贪心的角度入手,在它之上加可撤销操作。这大概就是不同点,但最后的代码都是一样的。
反悔堆
用时一定
用时为
P2949 [USACO09OPEN] Work Scheduling G
按截止时间排序。
接着对于一个工作,若截止时间内还有空,则直接选。
否则我们用一个堆,堆里面放选了的工作,按价值从小到大。
把价值最小的找到,看是否比当前工作的价值更劣,是则用当前工作将它替换。
价值一定
价值为
还是按截止时间排序。
还是用堆,把用时最长的找到,看用当前工作替换是否更优。
练习
显然先按照
相当于枚举一个走到的位置
注意到
则每次先把
看
否则,拿个堆,一直把最大的
注意是一直退掉,而不是用
若最后
对所有可行的答案取最大值即可。
反悔自动机
堆 反悔自动机
CF865D(简易老鼠进洞模型)
考虑在卖出股票时同时结算买和卖。
每一天都买,我们把它插入堆里,插入堆无代价。
每天再看能否卖,若把堆顶现在卖出有收益,则卖出。
但有问题:
我们用当前
但有可能后面有
解决方案就是,我们用
这样若后面有
就变成了用
双向链表+堆 反悔自动机
贪心思路:每次选最大的价值,选
然而选了
发现这种情况只能是
于是考虑选了堆顶
这样下次选到这个点,就相当于选
拿一个双向链表维护当前的前驱和后继即可。
老鼠进洞模型
有
第
老鼠可以左右移动,花费移动距离的代价。
要求最小化花费。
两个堆 基础模型
考虑洞没有权值,容量
先把老鼠和洞放在一起按坐标排序。
为了去掉绝对值的影响,我们把洞匹配老鼠和老鼠匹配洞分开考虑(即让右边匹配左边)。
设
用反悔贪心的思路,维护一个洞堆和一个老鼠堆,从左往右扫一遍。
如果当前扫到一个老鼠
那么取出洞堆的堆顶让它和这个洞匹配(初始时负无穷处有足够多个洞),设堆顶为
为了让右边没扫到的洞使这个老鼠能反悔,让它匹配右边一个新的洞。
那么如果它要匹配右边一个新的洞
如果扫到一个洞
考虑有没有老鼠要反悔来这个洞,从老鼠堆中取出堆顶
若这个洞原本应当让后面一个老鼠
贡献和即
若没有老鼠能反悔来这个洞,则直接插入
就像这样(
初始时
然后
后来
当然在权值上没有谁匹配谁的关系,我们只需使最后的权值和正确即可。
洞有权值
如果在基础模型的基础上,洞有权值
区别在于一个老鼠可能会匹配较远的洞。
老鼠匹配洞时有堆最优化,无影响。
而当洞匹配老鼠就有影响了。
首先是贡献为
考虑到我们让洞
所以,设
由于新增的
这时你可能会有疑问:
如果洞
那它应该要长成这样:
其中
然后
我们观察一下, 发现这种情况时不可能出现的,因为对于
那么对于上面的例子,最后一行会变成
所以我们不用担心取两次的情况。
洞有权值,也有容量
如果直接拆点,复杂度是
然而我们可以往堆中插入 pair
,多存一位个数。
具体来说,我们扫到一个老鼠匹配洞时,把那个洞的容量减一。
然后扫到洞时,我们记录有多少个老鼠来这个洞,设其为
往洞堆中插入的就无法一次性插了。
如果
注意我们并不需要撤销某只老鼠先前选的洞的容量,因为这是可撤销贪心,不用管它。
至于时间复杂度,一只老鼠只会从往左走转变为往右走至多一次,我们扫到洞时批量插入老鼠也保证了时间复杂度。
最后的复杂度就是一只
详见代码
提交记录。
int n,m; const int N=1e5+5; int X[N],Y[N],w[N],c[N]; ll ans; priority_queue<pair<ll,int>,vector<pair<ll,int>>,greater<>> a,b; signed main(){ usefile("snow"); read(n),read(m); ll csum=0; fo(i,1,n)read(X[i]); fo(i,1,m)read(Y[i]),read(w[i]),read(c[i]),csum+=c[i]; if(csum<n){ write(-1); return 0; } int i=1,j=1; fo(i,1,n)b.push({1e12,1}); while(i<=n||j<=m){ if(i<=n&&(j>m||X[i]<=Y[j])){ auto x=b.top(); ans+=X[i]+x.first; a.push({-2ll*X[i]-x.first,1}); b.pop(); if(x.second>1)b.push({x.first,x.second-1}); ++i; } else { int cnt=0; while(cnt<c[j]&&a.size()&&a.top().first+Y[j]+w[j]<0){ auto x=a.top(); int ca=min(c[j]-cnt,x.second); ans+=(ll)ca*(x.first+Y[j]+w[j]); a.pop(); if(ca!=x.second)a.push({x.first,x.second-ca}); b.push({-2ll*Y[j]-x.first,ca}); cnt+=ca; } if(cnt<c[j])b.push({-Y[j]+w[j],c[j]-cnt}); if(cnt) a.push({-Y[j]-w[j],cnt}); ++j; } } write(ans); return 0; }
wqs 二分优化
费用流的费用是流量的凸函数,我们可以利用 wqs二分 优化反悔贪心。
不会 wqs 二分可以看这篇博客。
首先设
知道它是一个凸函数后,使用 wqs 二分解决它。
二分斜率后,
考虑没有限制个数怎么做,用反悔贪心(也可以说是模拟费用流)。
考虑一张光盘从 B 工厂出来时结算它此前的所有代价。
从左往右扫一遍,我们搞一个堆存储所有 A 工厂的花费,然后对于 B 工厂,我们从堆里找一个 A 和它匹配,如果答案会变得更小那么就匹配。
但是可能有后面一个 B 匹配这个 A 更优的情况,于是我们用一个
我们开始要减掉
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下