单纯形法与线性规划

Preface

好久之前就想学学单纯形法了,因为据说用途非常广泛,而且最近恰好要做有关的题目

感觉还是挺高级的一个姿势吧,以下参考自,以及2016年的集训队论文,最后看的是bzt的板子,默认大家都知道线性规划是什么且具有一定线性代数的基础(好把没有也没有关系)


线性规划的标准型与松弛型

线性规划的标准型一般是这样的:

而松弛型是这样的:

桥豆麻袋,上面的标准型我能理解,这个松弛型是个什么鬼东西,怎么让变量更多了呢?

不要慌我们来观察一下,标准型简洁是简洁,但是它的约束符号是不等号(小于等于),而松弛型的约束符号是等号,众所周知等号比不等号要好处理的多

而它的转化也很简单,就是用一个松弛变量来表示不等式的差值,从而即保证了\(x_j\ge 0\)也化不等号为等号

同理这个操作对于大于等于号也适用,再网络流解线性规划也有用处,是非常常用的套路

然后我们像这样的等式\[x_{i+n}=b_i-\sum_{j=1}^n a_{i,j}x_j\]的左边的变量称为基变量,在松弛型里就是\(x_{n+1},x_{n+2},\cdots,x_{n+m}\),把右边的那些变量称为非基变量,在松弛型里就是\(x_1,x_2,\cdots,x_n\)


单纯形法的核心思想

接下来我们以一个例子入手单纯形法的核心思想

然后我们来考虑一种情况,当所有\(b_i\)非负时,我们显然可以令所有非基变量都为\(0\)从而得到一组初始解即\(x1=x2=x3=0\)

但是这样的解显然不一定是最优的(显然不是最优的),因为最上面的式子的非基变量前的系数是正的,根据高中必修课本上都有的线性规划知识,我们知道这样一定不是最优解,因为我们可以通过变量的替代使得一些系数为正非基变量取到更大的值

桥豆麻袋,你刚才说了变量的替代?这是什么意思?

很简单,我们得到的松弛型规划式子的本质其实是方程组,而方程组之间不同的变量是可以互相代换的,换句话说我们最后答案的表达式中不一定一定要用原来的\(x_1,x_2,\cdots,x_n\),等价地把\(x_{n+1},x_{n+2},\cdots,x_{n+m}\)换过去也是可以的

这就启发了我们如何判断一个解是否为最优解:最大化的式子右边所有非基变量之前的系数均非正时线性规划取得最优解

那么我们发现这个变量的替代操作很关键,它的核心就是把一个基变量\(x_{n+l}\)和\(x_e\)互换,即让原来的基变量成为非基变量,原来的非基变量称为基变量

先来考虑这个互化的问题,我们把这个操作叫做pivot(转轴),对于pivot(l,e),从式子的角度考虑:

\[x_{n+l}=b_l-\sum_{j=1}^n a_{l,j}x_j=b_l-\sum_{j=1}^n [j\not = e] a_{l,j}x_j-a_{l,e}x_e\]

则有:

\[x_e=\frac{b_l-x_{n+l}-\sum_{j=1}^n [j\not =e] a_{l,j}x_j}{a_{l,e}}\]

然后再考虑其他的\(x_{n+i}(i\not=l)\),把\(x_e\)用得到的式子全部代换掉就好了

知道了转轴操作之后我们还要知道和基变量里的那个变量转轴才会让答案变优

还是来看上面的例子,我们把系数是正的\(x_1\)进行代换,分别考虑三个限制:

\[x_4=30-x_1-x_2-3x_3\Leftrightarrow x_1\le 30\]

\[x_5=24-2x_1-2x_2-5x_3\Leftrightarrow x_1\le \frac{24}{2}=12\]

\[x_6=36-4x_1-x_2-2x_3\Leftrightarrow x_1\le \frac{36}{4}=9\]

由于\(x_1\le 9\)的限制最紧,因此我们要用\(x_1\)去代换\(x_6\),然后就可以得到下面新的线性规划:

woc现在目标函数的最大值已经被我们增大到\(27\)了,但是由于后面的非基变量的系数还是有正的,因此我们再给\(x_3\)做代换得到:

接下来还有\(x_2\)可以代换,得到:

此时由于所有非基变量的值都是负的,因此这就是线性规划的最优解


单纯形法的大致步骤

从上面的例子中我们大致对单纯形法的原理有了一定的了解,下面是它的伪代码(论文大法好):

来我们可以大体描述一下这个simplex的过程(我看是complex还差不多):

  1. 在最终的最大化式子中找到一个满足\(c_e>0\)的非基变量\(x_e\)
  2. 找到那个满足\(a_{l,e}>0\)且\(\frac{b_l}{a_{l,e}}\)最小的基变量\(x_{n+l}\)(结合前面所讲的,我们要找限制的最紧代换)
  3. 执行pivot(l,e),然后回到第一步

桥豆麻袋,万一死循环在这里了怎么办,那么意为着什么呢,必然有一个非基变量的系数在代换之后变成了\(0\)且其它的非基变量的系数都是负的

我们考虑它的几何意义,容易发现此时目标函数的梯度与某一个边界(即可行域的边界)正交了,因此说明目标函数的最优解有无限种(或者是无穷大)

那么我们就完成了单纯形法的主体部分了!


关于之前的一个假设

但是到这一步不知道大家是否记得,我们之前考虑的所有情况都在一个大前提下:\(b_i\)非负

而一旦\(b_i<0\)时,把所有的基变量代为\(0\)得到的非基变量不一定是一组合法解,那上面讲的东西就GG了

那么我们现在就需要一个初始化的操作,这个操作大致有两种实现,一种是论文里给出的构造辅助线性规划的方法,但是由于又要学习理论我比较懒,因此讲一种好写又好记的方法:

考虑我们的目标就是让所有\(b_i\)非负,那么我们找出\(b_i<0\)的基变量\(x_{n+i}\),然后在它的右边找一个系数为正的非基变量,然后我们在对它们进行转轴操作

结合前面的式子我们发现若此时\(b_i\)仍为负那么显然是无解了,否则实现了化\(b_i\)为正的过程


关于单纯形法的复杂度

emmm……这个其实我也分析不来的说,只知道`pivot的复杂度显然是\(O(nm)\)

由于一些奇奇怪怪的原因pivot的调用次数很少,因此一般在OI中能跑过几百的数据(怎么和它的好哥们网络流的复杂度有异曲同工之妙呢)

反正一般你能把一道题目化成单纯形法的时候直接干它就完了


模板

又到了喜闻乐见的放板子时间了,例题是UOJ上的版题UOJ #179. 线性规划,由于有毒瘤出题人卡精度卡时间等情况因此只要拿到97分就没什么问题了

#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstdlib>
#define RI register int
#define CI const int&
using namespace std;
const int N=25;
const double EPS=1e-8;
int n,m,tp; double a[N][N];
namespace SM //Simplex Method
{
    int id[N<<1]; double ans[N];
    inline void pivot(CI l,CI e)
    {
        RI i,j; swap(id[n+l],id[e]); double t=a[l][e];
        for (a[l][e]=1,i=0;i<=n;++i) a[l][i]/=t;
        for (i=0;i<=m;++i) if (i!=l&&fabs(a[i][e])>EPS)
        for (t=a[i][e],a[i][e]=j=0;j<=n;++j) a[i][j]-=t*a[l][j];
    }
    inline bool init(void)
    {
        for (;;)
        {
            RI i; int l=0,e=0;
            for (i=1;i<=m;++i) if (a[i][0]<-EPS&&(!l||rand()&1)) l=i; if (!l) break;
            for (i=1;i<=n;++i) if (a[l][i]<-EPS&&(!e||rand()&1)) e=i;
            if (!e) return puts("Infeasible"),0; pivot(l,e);
        }
        return 1;
    }
    inline bool simplex(void)
    {
        for (;;)
        {
            RI i; int l=0,e=0; double mi=1e9;
            for (i=1;i<=n;++i) if (a[0][i]>EPS) { e=i; break; } if (!e) break;
            for (i=1;i<=m;++i) if (a[i][e]>EPS&&a[i][0]/a[i][e]<mi) mi=a[i][0]/a[i][e],l=i;
            if (!l) return puts("Unbounded"),0; pivot(l,e);
        }
        return 1;
    }
    inline void solve(void)
    {
        RI i; for (i=1;i<=n;++i) id[i]=i; srand(20030909); if (init()&&simplex())
        {
            printf("%.8lf\n",-a[0][0]); if (!tp) return;
            for (i=1;i<=m;++i) ans[id[n+i]]=a[i][0]; for (i=1;i<=n;++i) printf("%.8lf ",ans[i]);
        }
    }
};
int main()
{
    RI i,j; for (scanf("%d%d%d",&n,&m,&tp),i=1;i<=n;++i) scanf("%lf",&a[0][i]);
    for (i=1;i<=m;++i) { for (j=1;j<=n;++j) scanf("%lf",&a[i][j]); scanf("%lf",&a[i][0]); }
    return SM::solve(),0;
}

例题

又到了喜闻乐见的放例题时间了:


Postscript

线性规划真是可怕呢……

原文地址:https://www.cnblogs.com/cjjsb/p/12266190.html

时间: 2024-10-12 01:22:22

单纯形法与线性规划的相关文章

单纯形法求解线性规划问题(C++实现代码)

1 单纯形法 (1) 单纯形法是解线性规划问题的一个重要方法. 其原理的基本框架为: 第一步:将LP线性规划变标准型,确定一个初始可行解(顶点). 第二步:对初始基可行解最优性判别,若最优,停止:否则转下一步. 第三步:从初始基可行解向相邻的基可行解(顶点)转换,且使目标值有所改善-目标函数值增加,重复第二和第三步直到找到最优解. (2) 用程序进行运算前,要将目标函数及约束方程变成标准形式. 于非标准形式须作如下变换: a) 目标函数为极小值min z=CX时,转换为max z=-CX形式:

省选板块

部分摘抄自网络 同样的,加粗是重点,星号是选学 图论 网络流(dinic,ISAP选一个,费用流写EK就行.*zkw费用流),二分图 点分治,边分治,*动态点分治 树链剖分,动态树,树分块 虚树,*prufer编码 *仙人掌算法 数据结构 带权并查集 Splay(作为平衡树和维护区间),Treap,替罪羊树 线段树(权值线段树),树状数组,*线段树合并 树套树 主席树,可持久化trie,*其它可持久化数据结构 二维线段树,*KDtree *舞蹈链,*二进制分组,*左偏树,*超哥线段树,*后缀平衡

【ACM算法纲要】【转自ALPC】

基本 C/C++.STL(vector.set.map.queue.string.algorithm) 枚举.贪心.递归.分治.递推.模拟 构造.位运算.常数优化 数据结构 队列.堆.栈.链表 排序(插入.冒泡.快速.归并.堆.桶.基数) 二分查找.散列表.并查集.哈夫曼树 排序二叉树.左偏树.平衡树(Splay/Treap/SBT) 树状数组.线段树.归并树.划分树.主席树.树套树 树链剖分.动态树 1/2维RMQ.LCA(在线/离线).稀疏表.字典树 字符串 KMP.扩展KMP AC自动机

算法纲要

基本 枚举.贪心.递归.分治.递推.模拟 STL(pair.vector.set.map.queue.string.algorithm) 构造.位运算.常数优化 数据结构 队列.堆.栈.链表 排序(插入.冒泡.快速.归并.堆.桶.基数) 二分查找.散列表 并查集.哈夫曼树 排序二叉树.左偏树.平衡树(Splay/Treap/SBT) 树状数组.线段树.归并树.划分树.主席树.树套树 树链剖分.动态树 1/2维RMQ.LCA(在线/离线).稀疏表.字典树 字符串 KMP.扩展KMP AC自动机 后

·算法」 纲要

基本 C/C++.STL(vector.set.map.queue.string.algorithm) 枚举.贪心.递归.分治.递推.模拟 构造.位运算.常数优化 数据结构 队列.堆.栈.链表 排序(插入.冒泡.快速.归并.堆.桶.基数) 二分查找.散列表.并查集.哈夫曼树 排序二叉树.左偏树.平衡树(Splay/Treap/SBT) 树状数组.线段树.归并树.划分树.主席树.树套树 树链剖分.动态树 1/2维RMQ.LCA(在线/离线).稀疏表.字典树 字符串 KMP.扩展KMP AC自动机

PLAN OF HEOI(unfinished)

(忽略分组名称)Au:动态树分治/数位dp/博弈论/整体二分/杜教筛/计算几何/fft/ntt/fwtAg:可持久化重量平衡树/线段树分治/线段树合并/最短路树/最短路DAGCu:三分高:矩阵/行列式/矩阵树定理/(ex)BSGS/群论(Burnside引理/Polya定理......)/随机算法(爬山算法/模拟退火/随机增量法......)/生成函数/多项式/单纯形法解线性规划/原根强:网络流(线性规划/数据结构优化/NB模型......)/字符串算法(广义/可持久化......)/可持久化数

bzoj3550: [ONTAK2010]Vacation&amp;&amp;bzoj3112: [Zjoi2013]防守战线

学了下单纯形法解线性规划 看起来好像并不是特别难,第二个code有注释.我还有...*=-....这个不是特别懂 第一个是正常的,第二个是解对偶问题的 #include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; const double e

运筹学——线性规划及单纯形法求解

运筹学——线性规划及单纯形法求解 1. 线性规划的概念 线性规划是研究在一组线性不等式或等式约束下使得某一线性目标函数取最大(或最小)的极值问题. 2. 线性规划的标准形 特点:目标函数求极大:等式约束:变量非负. 令 则线性规划标准形的矩阵表达式为: 约定: 如何化标准形: (I) 目标函数实现极大化,即,令,则: (II)约束条件为不等式 约束条件为“” 不等式,则在约束条件的左端加上一个非负的松弛变量: 约束条件为“” 不等式,则在约束条件的左端减去一个非负的松弛变量. (III)若存在无

线性规划(单纯形法)知识整理

内容有点多,自己写是不可能的,下面挂两篇博客,复习的时候可以看看 fjzzq’s blog hrwhisper’s blog 该算法的时间复杂度很迷,精度也很奇怪,$uoj$的题用$eps=1e-13$就炸了,用$1e-11$较好 这里挂两个模板,对应不同的标准型,需要的时候可以选一个用,有时可以避免求基本解 但注意若$b_i < 0$,无论哪个板都要先初始化求基本解 模板: $Case 1$: 最大化 $\sum_{j=1}^nc_jx_j$  满足$m$条约束 $\sum_{j=1}^na_