zoukankan      html  css  js  c++  java
  • 模拟费用流学习笔记

    前情提要:上个学期开了一道老鼠进洞的题,然后一直咕到了现在……

    大概就是把费用流建出来,然后利用一些性质,贪心来实现整个费用流的过程,即找最短路,退流等。

    很难想到,但大多都是费用流比较显然,然后数据范围比较大的题(目前做到的好像都是和“匹配”相关的)

    参考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$ 加入可供后面洞反悔的鼠。

    例题

    XJ省选模拟8-B

    感觉这个难度放第一题有点不合适啊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));
        }
    }

     NOI2019序列

    [ICPC2018 WF]Conquer The World

    后两题的题解代码网上自取,有手就行

  • 相关阅读:
    JTree单击事件
    hibernate、easyui、struts2整合
    ubuntu中wifi显示被硬件禁用的解决方法
    idea导入svn项目
    Intellij IDEA常用配置详解
    HBase 写优化之 BulkLoad 实现数据快速入库
    Spark性能优化之道——解决Spark数据倾斜(Data Skew)的N种姿势
    avro序列化详细操作
    wordcount代码实现详解
    idea配置maven
  • 原文地址:https://www.cnblogs.com/Forever-666/p/14623282.html
Copyright © 2011-2022 走看看