模拟费用流学习笔记
前情提要:上个学期开了一道老鼠进洞的题,然后一直咕到了现在……
大概就是把费用流建出来,然后利用一些性质,贪心来实现整个费用流的过程,即找最短路,退流等。
很难想到,但大多都是费用流比较显然,然后数据范围比较大的题(目前做到的好像都是和“匹配”相关的)
参考WinnieChen的博客,代码实现可以在那边看orz
老鼠进洞,代价为距离,求最小代价
problem 1:每个老鼠只能往左走
拆贡献,每一个老鼠贡献肯定是 $x_i$ 所以每次匹配只要取能取的最小的 $-y_i$ 即可
problem 2:老鼠左右不限
早上看了 $laofu$ 的WC课件 $dp$ 的做法,感觉就是一个利用单调性的 $dp$ 优化,结果下午才发现原来他把网络流相关的做法放到最后面了…
先整体排序以后 $dp$ ,除了0之外的决策唯一是因为状态最终的意义是到0,都是等待后面的鼠或者洞把它抵消变成0,所以新的值取值一定是负,那么越后面取一定更优
然后用差分数组 $d_{i,j}$ 表示 $f_{i,j}-f_{i,j-1},j>0$ ; $f_{i,j}-f_{i,j+1},j<0$
感性理解一下这个 $f$ 的值是凸的(多出来的尽量先选负值更小的,后面越来越大),所以只要对于 $d_{i,j}$ $j$ 大于等于0和小于0各维护一个堆,取最小值,就可以动态维护 $f_0$ 的值并且加入新的决策值了(新的 $d$ 只会从零点产生)
考虑这个东西,我们可以对于老鼠和洞建出一个网络流。
可以发现堆里面的三个操作实际就是用贪心实现了网络流:
1.假如是老鼠,那么选左边的最近的洞匹配,即网络流的增广。
2.假如是洞,那么看它是否可以反悔前一个老鼠的操作,即让老鼠向右匹配,就是退流以实现最短路。
3.向右匹配可能会带来的后果是再右边的要向左匹配,会产生交叉,实际答案是把交叉部分剪掉,要在 $Q1$ 中新插入一个洞,相当于反向边。
补充:对于最小费用最大流问题,可以对于每个老鼠先带一个权值 $-\infty$ ,后面再加回来,就是一个最小费用流问题了。(虽然不知道这道题有什么用qaq)
problem 3:老鼠向左走,每个洞有代价,老鼠不一定要匹配,最大化代价
这个东西拿个堆随便做,拆贡献,老鼠每次选代价最大的,为了反悔再插入 $-x_i$
problem 4:每个洞可以进 $b_i$ 次
还是一样用堆搞,只不过再存一个剩余容量,直到全部被减成0了再退出堆。
分析复杂度,每一个老鼠最多只会上手自己匹配一次,然后被堆反悔一次,还是 $O(nlogn)$ 的
problem 5:老鼠无限分身,洞容量无限,老鼠和洞都要被匹配
对于这种的流量无限,但是必须匹配最小代价的题有一个trick:先对于每一个点拉出一个流量为1权值为 $-\infty$ 的第一类点,剩下的为流量无限的第二类点,可以保证所有都被匹配,然后还是一样用堆做,此时洞和鼠已经一样了,所以鼠也可以使洞反悔。
因为不可能两个第二类点匹配,用分身肯定只是让周围的更近匹配,所以反悔操作次数还是 $O(n)$ 的。
problem 6:在problem2的基础上加上每个洞有代价
考虑problem3的反悔,就是意味着一个鼠它如果匹配了右边的洞,有可能因为代价原因继续向右反悔,所以在右边的洞匹配左边的点时,不仅要在 $Q1$ 加入反向边新产生的洞,还要在 $Q0$ 加入可供后面洞反悔的鼠。
例题
感觉这个难度放第一题有点不合适啊qaq
建立费用流,数轴上顺序连出左右括号,起点往左括号连一个费用边,右括号向终点连一个费用边,建一个虚点限制流量。
然后模拟这个过程:每次跑出一个最短路然后增广。
发现这个图的费用边是不可能被退掉的,所以可以贪心选择。
考虑顺着流,那么就相当于选择一个左右括号,如果反着流,那么就是选择一个右左括号,要求反向边流量至少为1,正向边流量没有限制。
所以可以建立出一颗线段树维护,每次选出代价最小的左右或右左括号。
对于左右括号,因为没有限制,那么直接维护区间答案,单个左括号,单个右括号。
对于右左括号,考虑对于0的限制的比较经典套路是维护区间反向边最小值。
我们把右左括号分成自由的和受限制的,自由的直接更新区间答案,受限制的看区间最小值大于0才更新答案。
这里受限制是指有最小值在右左括号之间,因为不是最小值意味着一定大于0。
所以维护上述的东西,我们可以处理出在分别最小值左、右的单个左右括号,合并的时候讨论一下反向边最小值大小即可。
然后维护这些点的位置,找出两个点后把边权设为 $\infty$ ,增广把区间全部的反向边加1或减1。
大概就是把problem4和6组合一下,然后被hack了,在继续向右反悔的过程中不能一个一个来,不然可以刚开始所有点都匹配第一个洞,然后洞一个一个反悔过去,复杂度就是 $O(n^2logn)$ ,要注意把这些相同的绑在一起搞。
Q1.push(mk(INF,inf)); for(int i=1;i<=n+m;i++){ if(a[i].typ==1){//鼠 pii tmp=Q1.top(); Q1.pop(); ans=ans+tmp.first+a[i].x; Q0.push(mk(-tmp.first-2*a[i].x,1)); tmp.second--; if(tmp.second) Q1.push(tmp); } else{//洞 int cnt=0; while(!Q0.empty()&&Q0.top().first+a[i].x+a[i].w<0&&a[i].c){ pii tmp=Q0.top(); Q0.pop(); int now=min(tmp.second,a[i].c); Q1.push(mk(-tmp.first-2*a[i].x,now)); ans+=(tmp.first+a[i].x+a[i].w)*now; a[i].c-=now; tmp.second-=now; if(tmp.second) Q0.push(tmp);//!!! cnt+=now; } if(cnt) Q0.push(mk(-a[i].x-a[i].w,cnt));//!!! if(a[i].c) Q1.push(mk(-a[i].x+a[i].w,a[i].c)); } }
[ICPC2018 WF]Conquer The World
后两题的题解代码网上自取,有手就行