• 【复习笔记】模拟费用流


    是去年写的了 存在笔记本里现在才发现(汗
    过段时间会再补一些内容吧

    兜兜转转还是你d(´ω`*)

    从一个奥妙重重的基本问题开始

    给定(n)只老鼠,(m)个洞,求一个满足要求的匹配的代价。

    Sub 1

    限制:只能向左走 代价为距离

    解法:直接排序 每个找左边空的最近的就行了

    Sub 2

    限制:无限制 (O(n))复杂度 代价为距离

    解法:

    dp方程(f[i][j])表示前(i)个位置 有(j)个洞要匹配 (j)可负((-j)只老鼠等洞)

    显然的想法是拆开距离 然后交叉匹配一定不优于不交叉匹配 所以我们把交叉匹配方式去掉

    1. 对于老鼠
      • $jge 0, f[i][j]=f[i-1][j+1]+x[i] $
      • (j<0, f[i][j] = f[i-1][j+1]-x[i]) (因为所有老鼠都必须进去 所以不需要决策)
    2. 对于洞
      • (j>0 , f[i][j] = min(f[i-1][j-1] -y[i],f[i-1][j]))
      • (jle 0, f[i][j] = f[i-1][j-1]+y[i])(y[i])满足单调递增,所以必定直接转移最优)

    这样转移是满的(O(n^2)) 考虑通过性质优化

    对于洞的优化 对于决策(j>0)是 满足第一种转移更优 因为(y[i]>y[i-1]) 所以一定可以用(y[i])替换当时的转移从而变得更优【没匹配的左边的洞往右移动显然更优】 考虑到需要完全匹配 所以我们只用维护(f[i][0])就可以了 其余的一定是直接转移最优

    由于所有转移都是直接转移 所以用两个栈维护最近的没匹配的鼠/洞 然后用tag维护全局加减就好啦

    代码片段:

    stack a,b;
    int tag1=0,tag2=0,f0=0;
    for(int i=1;i<=n;i++)
    {
        if(op[i]==1)//洞
        {
    		tag1-=a[i],tag2+=a[i];
            a.push(f0+a[i]-tag2);
            f0=min(b.top()+tag1-a[i],f0);
            b.pop();
        }
        else//鼠
        {
            tag1+=a[i],tag2-=a[i];
            b.push(f0+a[i]-tag1);
            f0=a.top()+tag2-a[i];
            a.pop();
    	}
    }
    //我也没大搞懂(。
    

    还有一种比较常见的(O(nlgn))做法

    由于不存在交叉匹配 所以我们考虑贪心

    我们维护以0为原点的差分数组 (d[i][j])

    (d[i][j]=f[i][j]-f[i][j-1],j>0; d[i][j]=f[i][j]-f[i][j+1] j<0)

    对于老鼠

    (f[i][0]=f[i-1][0]+d[i-1][1]+a[i],d[i][j]=egin{cases}d[i-1][j+1] , j>0 || j<-1\ -d[i-1][1]-a[i] * 2 ,j=-1end{cases})

    对于洞

    (f[i][0]=min(f[i-1][0],f[i-1][0]+d[i-1][-1]+a[i]))

    讨论(d[i-1][-1]+a[i])(0)的关系

    1. (d[i-1][-1]+a[i]<0)

      (f[i][0]=f[i-1][0]+d[i-1][-1]+a[i],d[i][j]=egin{cases}d[i-1][j-1],j>1||j<0 \-d[i][-1]-a[i] imes 2 ,j=1end{cases})

    2. (d[i-1][-1]+a[i]ge 0)

      (f[i][0]=f[i-1][0],egin{cases}d[i-1][j-1],j>1||j<0 \-a[i] ,j=1end{cases})

    发现这玩意是凸的 所以每次新添加的一定是最小的差分 直接用堆维护差分表

    priority_queue<int> lf,rg; int f0=0;
    for(int i=1;i<=n;i++)
    {
        if(op[i]==1)//洞
        {
            if(lf.top()+a[i]<0)
                f0=f0+lf.top()+a[i], rg.push(-lf.top()-a[i]*2), lf.pop();
            else	rg.push(-a[i]);
        }
        else
            f0=f0+rg.top()+a[i], lf.push(-rg.top()-a[i]*2), rg.pop();
    }
    

    还有一种思路就是堆里放的是可以后悔的 取出来可以反悔

    Sub 3

    限制:只能往左走 对于每个洞还有额外的代价(v[i])

    解法:

    按照(a_i)排序 然后维护洞的时候用(v_j-y_j)排序就好了 可能会有反悔的添加一个(-x_i)就好了

    题目:bzoj4977 跳伞求生

    代码:戳我

    Sub 4

    限制:左右都可以走 每个洞有额外代价(v[i])

    解法:

    还是按照(a_i)(老鼠的(x_i),洞的(y_i))排序 遇到老鼠找一个代价最少的洞

    我们按照费用流【最小权匹配】的思路走 我们先是贪心选 然后会遇到退流问题

    1. 老鼠(a)

      必须匹配 所以从洞堆里拿出来一个配上对 然后再在鼠堆里填上反悔操作(-x[i]*2-w[j])(w[j])表示匹配上的洞提供的代价)【其实是洞堆里添了一个(inf - x[i]*2) 因为鼠必须匹配 所以别的鼠来抢这个洞的时候这只鼠就无家可归了 所以其实就是非法匹配了

    2. (a)

      从老鼠堆里看最优的是否可以取

      • 可以的话 权值加上(w[j]+x[i]+v[i]) 洞堆里添一个(-w[j]-2*x[i]) 鼠堆里添上一个(-x[i]-v[i])(因为鼠还是必须要有匹配的 而洞可以直接扔掉)
      • 不可以的话 洞堆里添一个(-x[i]+v[i])
    priority_queue<int> a,b; int f0;// a鼠堆 b洞堆 【小根堆
    for(int i=1;i<=n;i++)
        if(op[i]==1)// 洞
        {
            if(a.top()+x[i]+v[i]<0)
            {
                f0+=a.top()+x[i]+v[i];
                b.push(-a.top()-2*x[i]);
                a.pop();
                a.push(-x[i]-v[i]);
    		}
            else	b.push(-x[i]+v[i]);
        }
    	else
        {
            f0+=b.top()+x[i];
            a.push(-b.top()-x[i]*2);
            b.pop();
    	}
    

    Sub 5

    限制:分身术!每个洞其实有(c_i)的容量 每个位置叠了(d_i)只老鼠(均为(1e9)级别)

    解法:

    直接用pair记录流量(没 想 到 吧)

    势能分析证出来是(O(nlgn))带常数【我要是会证我就是个茄子

    题目:UOJ455 雪灾与外卖

    代码:戳我

    Sub 6

    限制:上树

    解法:

    自底向上合并就好了 可并堆维护

  • 相关阅读:
    Winform 让跨线程访问变得更简单
    Winform 单实例运行
    webservice 测试窗体只能用于来自本地计算机的请求
    使用srvany.exe把程序安装成windows服务
    学习ExtJS4 常用控件
    [leetcode]Unique Binary Search Trees
    [leetcode]Subsets II
    [leetcode]Subsets
    [leetcode]Valid Number
    [leetcode]Permutation Sequence
  • 原文地址:https://www.cnblogs.com/hanyuweining/p/14697005.html
Copyright © 2020-2023  润新知