线性规划——单纯形算法の板子

以下内容全是口胡,请谨慎观看 ——From ATP

什么是线性规划?

参见高中数学必修五第三章

根据ATP的理解。。。线性规划就是设出一些变量,给出一些对于变量取值的限制,同时给出一个与这些变量相关的目标函数,要求在满足限制的前提下最大化或最小化目标函数。

举个例子:设有变量 x1,x2,x3 ,那么下面这一坨东西:

Maxmize   5x1+2x23x3
满足约束:
2x1+3x35
3x24x38
4x12x27
x1,x2,x30

就是一个线性规划啦。。(上面那一坨东西是ATP随便打上去的有没有解就不一定了= =)

形式化地,设 x 为n维列向量代表设定的变量, A 为m*n的矩阵代表每个限制中每个变量的系数, B 为m维列向量代表每个限制中的常数项, C 为n维行向量代表目标函数的系数。举个栗子说,对于上面那一坨东西,它的各个元素表示如下:

A=204032340

B=587

C=[523]

我们在这里仅讨论线性规划的标准型。线性规划的标准型表示如下:
Maxmize    Cx
S.T.   AxB,x0

但是很多时候好像遇到的线性规划并不是标准型?难道它们不能做吗。。。其实并不是,在大多数情况下,不是标准型的线性规划都可以通过做一些适当的转化表示成标准型。比如……

  1. 目标函数要求最小化( Minmize )?
    似乎可以把所有系数取反,求得答案以后再反回来?
    在某些情况下,下面提到的“对偶定理”会更加好用。

  2. 首先线性规划的限制应该都是大于等于或者小于等于这种形式的(不然计算机怎么搞出“无限接近”这种类型的答案来= =),但是在标准型里面限制都要求是小于等于,如果遇到别的符号?
    如果是带“ ”的限制,直接把所有系数取反就可以;而如果是带“=”的限制,例如 a=b ,我们可以转化成 ab ba 两个限制。

总之通过类似这样的变换我们可以把任意的线性规划转化为标准型,并且它们的最优解都是不变的。

求解线性规划——单纯形算法

求解线性规划最常用的算法是单纯形算法。它的理论时间复杂度是指数级别的。。但是跑得奇快无比啊并且也基本上卡不掉。。所以放心大胆用就是了。。

单纯形算法的几何意义很多地方都有解释。。但是这里ATP主要解释它在代数方面的原理。

首先,为了对线性规划使用单纯形算法,我们需要把线性规划转化为“松弛型”,也就是说把所有的不等式变为等式。以下都用m来表示限制个数,n来表示变量个数。

为了做到这一点,我们对于每一个形如 i=1nAk,ixiBk 的限制增加一个叫做“基变量”的东西,把它称作 yk 。我们保证 yk0 ,那么现在每条限制都变成了形如 yk+i=1nAk,ixi=Bk 的形式!

相对的那我们就把所有 xk 叫做“非基变量”好了。显然目标函数只会涉及到非基变量。现在我们做出一个假设就是如果把所有 xi 都取到0,那么它是一组能够满足所有限制条件的可行解。——实际情况下很多时候并不是这样的,这种情况在下面的对偶原理部分中进行讨论,现在先假设它是成立的。

那么我们把所有的 xi 都取0,可以计算出目标函数的一个值。但是显然它不一定是最优的。观察目标函数,有些 xi 的系数是大于0的,那么如果我们能够让这些变量增大,我们就可以得到一组更优的解。但是如果目标函数里面不存在系数为正数的 xi ,那么如果把所有 xi 都取0就能够得到最优解——所以前面那个假设是必要的。而单纯形算法就是要对目标函数和约束条件作出适当的变形,在不改变最优解的情况下把目标函数里面所有的系数都变成负数。

先贴代码,在下面解释:

double Simplex(){
    int l,e;
    double t;
    while (true){
        e=n+1;
        for (int i=1;i<=n;i++)
          if (C[i]>eps){e=i;break;}
        if (e==n+1) break;
        t=inf;l=0;
        for (int i=1;i<=m;i++)
          if (A[i][e]>eps&&t>B[i]/A[i][e]){
              t=B[i]/A[i][e];l=i;
          }
        if (t==inf) return inf;
        pivot(l,e);
    }
    return v;
}

上面的Simplex过程是单纯形算法的主过程。首先每次先找到第一个系数大于0的非基变量。这里有一个叫做Bland法则的东西,就是说一定要选第一个系数大于0的,不然容易被特殊数据卡成死循环。。然而ATP并没有试过= =

选中的这个非基变量就是上面的e。如果没有找到这样的变量,说明算法已经完成了,目标函数中的所有系数都变成负数了,就可以退出了。否则我们要把目标函数中 xe 的系数变成负数。上面提到过,当前的初始可行解为所有变量都取0,那么我们想要做的是尽量增大某个系数为正数的变量,也就是当前选择的 xe 。但是 xe 最大可以增大到多大呢?这就要看约束条件了。接下来的一步就是枚举所有约束条件了。考虑每个含有 xe 的约束条件 i=1nAk,ixiBk ,因为已经保证所有的 xi 都是大于0的,所以要让 xe 尽量增大当然要把除了 xe 的所有变量都取0。那么当前约束下 xe 能够取到的最大值就是 BkAk,e

不同的约束条件产生的 BkAk,e 是不一样的,显然为了正确地“增大” xe ,我们需要找到所有约束条件产生的最小的 BkAk,e 。如果找不到,就说明 xe 最大可以增大到正无穷,这个线性规划就是无解的。

否则我们能够找到一个约束条件 l ,满足 BlAl,e 是最紧的那个限制。那么我们就要开始对约束条件进行变形了。变形能够进行的基础就是因为添加了基变量 yl 使得所有约束条件变成了“=”的形式,我们可以随便把每一项挪来挪去加加减减,都不会改变这个约束条件的本质。

基变量 yl 的作用还不止如此,我们接下来要做的变形工作实际上是要用 yl 替换掉 xe ,也就是改变基变量与非基变量的集合,使得 yl 成为新的一个非基变量, xe 成为新的一个基变量。这样一来就肯定要把整个约束条件表示成为 xe=... 的形式,而因为一开始 xe yl 在等号同侧而现在变成了异侧,那么 yl 的系数肯定就变成了相反数。那把它往目标函数里一代的时候,就会有一个基变量的系数变成负数了!

于是上面的pivot过程就是来做这样的变形的。这玩意儿好像学名叫“转轴”操作,好像来源于单纯形算法的几何意义。。不管它不管它。

pivot过程是这样的:

void pivot(int l,int e){
    B[l]/=A[l][e];
    for (int i=1;i<=n;i++)
      if (i!=e) A[l][i]/=A[l][e];
    A[l][e]=1/A[l][e];
    for (int i=1;i<=m;i++)
      if (i!=l&&fabs(A[i][e])>eps){
          B[i]-=B[l]*A[i][e];
          for (int j=1;j<=n;j++)
            if (j!=e) A[i][j]-=A[l][j]*A[i][e];
          A[i][e]=-A[l][e]*A[i][e];
      }
    v+=C[e]*B[l];
    for (int i=1;i<=n;i++)
      if (i!=e) C[i]-=C[e]*A[l][i];
    C[e]=-C[e]*A[l][e];
}

这里的 l,e 意义和上面相同。在pivot过程的第一步,我们需要先对选定的这个约束条件 l 进行变形,把它由 yk+i=1nAl,ixi=Bl 的形式变成 xe=1×i=1,ienAl,ixiyl+Bl 的形式,这样才能把它扔到别的含有 xe 的约束中进行替换。

为了替换方便我们要把 xe 的系数变为1,那么就要把所有的变量都除以 xe 原来的系数 Al,e 。而对于 Al,e 这个位置的数字我们又需要特殊处理。因为我们要把 xe 替换为 yl ,所以还要把 Al,e 这个位置变成 yl 应该有的系数。 yl 原本系数为1,现在当然就要变成 1Al,e 啦。

这样我们就完成了过程的第一步,也就是上面代码里前4行的内容。

接下来我们要对所有除了约束条件 l 以外的约束条件进行变形。枚举所有不是 l 并且 xe 的系数不是0的约束条件,我们要把 xe=... 这个式子给带进去消掉 xe 。设当前的约束为 i ,那么我们就可以把约束i中含有 xe 的项 Ai,exe 给利用上面提到的式子给 xe=1×i=1,ienAl,ixiyl+Bl 替换掉。

很容易发现替换以后约束条件 i 中除了 Ai,e 以外的项j,全都减去了 Ai,eAl,j ,而常数项 Bi 减去了 Ai,eBl 。而同样的,对于 Ai,e 这一项我们要特殊处理,把它变成现在 yl 这个变量该有的系数,也就是 Al,eAi,e

最后是对于目标函数的处理,同样是把 xe 消掉,把 yl 引入。而我们用一个单独的变量v来记录目标函数中的常数项,一开始目标函数中是没有常数项的,随着我们的代换会出现一个渐渐增大的常数项。显然 Bl 就是贡献常数项的东西,每次操作它会为常数项贡献 CeBl 这么多。然后接下来的代换过程就是和上面替换 Ai,e 的时候相似的了,这里不再赘述(ATP觉得自己啰嗦的已经够多的了qwq)

最后当目标函数中所有系数都变成负数的时候,记录的v就是线性规划的最优解啦。

对偶原理

前面我们所有的讨论都基于一个假设:所有非基变量取0是一组可行解。

但是很多时候我们会碰到这样的线性规划:
Minmize    Cx
S.T.   AxB,x0

把所有系数取反?但是取反这种操作是不会改变约束条件的本质的。这个时候我们就会用到对偶原理了

形式化的,对偶原理就是说:

Minmize    Cx
S.T.   AxB,x0

Maxmize    BTx
S.T.   ATyCT,y0

这两个线性规划是等价的。

显然,如果我们遇到了一个这种所有非基变量取0不是可行解的线性规划,我们可以用对偶原理把它“转”一下,就会变成一个所有非基变量取0是可行解的线性规划,就可以用单纯形求解了。

证明?好像不大会用到,ATP也不会。。。

不过想要理解一下对偶原理的话,这篇文章还是不错的[戳这里]。。。

贴几个板子题吧

  • BZOJ1061 志愿者招募
  • BZOJ3112 防守战线
  • BZOJ3265 志愿者招募加强版
  • BZOJ3550 Vacation

据说网络流都可以用线性规划做?

参考曹钦翔《线性规划与网络流》。。。

ATP其实没怎么看那个。。。

主要问题是感觉里面说的基本上就是如何把现成的网络流模型改造成线性规划。。。

但是既然都有了网络流模型干嘛还要改线性规划啊。。。(゚Д゚*)ノ

据说线性规划也可以用费用流做?

这种方法是ATP一开始看线性规划的时候稀里糊涂学过来的。。也没大用过。。在这里提一句吧。。

例:BZOJ1061 志愿者招募

用费用流做线性规划的时候,我们要做的第一步仍然是列出不等式并且把它们转化成松弛型。接下来以这个题的样例来举例说明操作过程。

首先设每一种志愿者的招募人数为 xi ,我们可以列出这样的三个松弛型约束条件:

  • P1x1y1=2
  • P2x1+x2y2=3
  • P3x2+x3y3=4

添加两个约束条件 P0=0,P4=0 ,然后用 Pi+1Pi ,我们就得到了一组新的等式:

  • P1P0x1y1=2
  • P2P1x2y2+y1=1
  • P3P2x1+x3y3+y2=1
  • P4P3x2x3+y3=4

我们把它们化成这种形式是有原因的,因为我们求解的方法是网络流,而这组等式满足一个十分关键的性质——它们相加的和等于0。这可以和网络流中的流量平衡结合起来。那么我们可以把上面的每个等式看成一个点并且按照变量的正负进行连边,并且设定“流出为正,流入为负”,那么源点S作为唯一只有流出流量的点,它流出的流量可以看成等式右边那些正数;汇点T作为唯一只有流入流量的点,它流入的流量可以看成等式右边那些负数。然后我们找到每个变量 xi yi 正值和负值出现的等式 Ps Pt ,然后连一条 Ps Pt 的边。而这道题因为有最小化费用的要求,所以我们在边上加费用。对于每个 xi ,每使用一单位流量就要增加 vi 的费用,而 yi 是添加的辅助变量,对结果没有影响,所以费用为0即可。

其实这个过程就是将费用流问题转线性规划问题的逆过程。。

你可能感兴趣的:(板子们)