• 线性规划与单纯形


    线性规划与单纯形

    0x00 前言

    • 二轮前心有点乱,做不动题,来写写算法笔记调整一下心情
    • 你可能需要:
    • 矩阵基础
    • 高斯消元基础
    • 高中线性规划基础
    • 来理解这个东西

    0x01 简介

    • 线性规划描述这样一类问题:有一些变量 $ x_{i} $ ,有一个目标函数 $ C = sum c_{i}x_{i} $, 有一些限制 $ sum a_{i,j}x_{j} leqslant or geqslant b_{i} $,我们要最大化/最小化C
    • 比方说,你要找一个npy,npy有一个心情值和对你的喜爱程度,有2种物品,你给ta买一个物品i会花掉你$ c_{i} $的钱,增加npy $ a_{i,2} $ 的心情值,$ a_{i,2} $ 的喜爱程度,问你最少要花多少钱才能让ta的心情值>=b1,对你的喜爱程度>=b2,为了便于理解,这里的变量和上面的定义是对应的
    • 这个问题就相当于,设你买了x1个物品1,x2个物品2,要求在

    [a_{1,1}x_{1}+a_{2,1}x_{2} geqslant b_{1} ]

    [a_{1,2}x_{1}+a_{2,2}x_{2} geqslant b_{2} ]

    的前提下,最小化

    [C = c_{1}x_{1}+c_{2}x_{2} ]

    • 这就是一个线性规划的典型形式

    0x02 简化版问题

    • 我知道你们都学过简化版线性规划(平面版的),所以我就不讲那个愚蠢的用一条直线去切可行解区域的方法了。

    0x03 松弛型与标准型

    • 线性规划有两种规范的形式,即松弛型与标准型
    • 这一条讲给有一定基础的人:我这里的标准型和松弛型可能和大学课本里的定义不一样,我以我的为准
    • 先讲一下标准型,所谓的标准型就是在刚才的定义的基础上

    有一些变量 $ x_{i} $ ,有一个目标函数 $ C = sum c_{i}x_{i} $, 有一些限制 $ sum a_{i,j}x_{j} leqslant or geqslant b_{i} $,我们要最大化/最小化C

    • 加一些更严格的限制条件,变成这样

    有一些变量 $ x_{i} geqslant 0 $ ,有一个目标函数 $ C = sum c_{i}x_{i} $, 有一些限制 $ sum a_{i,j}x_{j} leqslant b_{i} $,我们要最大化C

    • 事实上,把普通形式转化为标准型是不难的,有几个问题,我们分别讨论怎么解决
    1. $ x_{i} $ 可能没有限制
      把 $ x_{i} $ 全部拆成 $ x_{i} = x_{i+1}-x_{i+2} $,然后 $ x_{i+1},x_{i+2} geqslant 0 $ 稍微思考一下就发现这很正确
    2. 有 $ x_{i} < 0 $ 这种坑爹限制
      把 $ x_{i} $ 全部换成 $ -x_{i} $
    3. 可能限制是 $ sum a_{i,j}x_{j} geqslant b_{i} $ 的形式
      不等式整个乘-1取反
    4. 可能要最小化C
      取反取反!
    5. 限制可能是小于或者大于,没有等于
      分情况讨论,如果是浮点数,两者没有区别
      如果是整数,直接+1/-1即可
    • 然后我们就得到了标准形式,然并卵
    • 我们还要讨论一下松弛形式
    • 如果你学过高斯消元,你会知道,n个线性无关的方程可以解出n个变量,但是n个不等式啥都干不了
    • 但是我们可以给它加上一些松弛变量,然后让不等式变成等式
    • 显然

    [a_{1,1}x_{1}+a_{2,1}x_{2} leqslant b_{1} ]

    [a_{1,1}x_{1}+a_{2,1}x_{2}+x_{3} = b_{1} ]

    • 是等价的,这里就可以看出为啥要统一成小于等于了,因为还有 $ x_{i} geqslant 0 $ 的性质,在小于等于的基础上 $ +x_{3} $ 才是不违反限制的
    • 然后就都变成等式啦!

    0x04 基解与可行解

    • 但是这里有问题了,假如一共有n个变量,m个限制,因为松弛的需要,又额外加了m个变量,于是现在又n+m个变量,m个方程,根据克拉玛定理(或者说,显然),这个方程不止一组解,我们不妨先弄一组解出来

    • 我们来考虑一下系数 $ a_{i,j} $ 的矩阵,就用刚才那个npy问题的矩阵好了

    [egin{bmatrix} a_{1,1} & a_{2,1} & 1 & 0 \ a_{1,2} & a_{2,2} & 0 & 1 end{bmatrix} ]

    • 注意这个是加了松弛变量以后的矩阵
    • 发现前面那一堆a不太好弄,指不准行列式就是0,但是后面有一个I,好解&行列式不会为0,那么$ x1 = 0,x2 = 0,x3 = b1,x4 = b2 $ 是一组很不错的解
    • 然后呢?然后这个解没什么卵用(C是0),而且可能不可行(b1,b2可能小于0)
    • 这样的一个解里有m个变量不是0(叫做基变量),n个变量是0(叫做非基变量)
    • 我们把任何一组这样的解叫做基解,如果它可行,则称作基可行解
    • 不妨来考虑一下这个东西的几何意义
    • 你已经知道了二维平面上的最优解总是在凸包顶点上(或者会有无穷多最优解)
    • 事实上,这相当于在一个n+m维空间里切n+m维的超多面体,理解一下这个,这很重要
    • 脑洞一下就能发现,最优解也类似的,肯定在超多面体的顶点上
    • 而我们解出来的基解一定是在顶点上的
    • 通过一些步骤我们可以让解在多面体上移动,最终跑到最优解
    • 好,几何到此为止,因为再下去就不可言传了,你得自己体会,而我越讲你越乱

    0x05 单纯形法

    • 假设有这样一个线性规划
    • 在这里为了简便,我们假设所有 $ b_{i} geqslant 0 $,小于0的后面会讲到
    • 为了易于理解与书写,我们把变量设成具体的数字

    [C = x_{1}+2x_{2} ]

    [2x_{1}+x_{2} leqslant 2 ]

    [-x_{1}+x_{2} leqslant 3 ]

    • 转化成松弛型:

    [C = x_{1}+2x_{2} ]

    [2x_{1}+x_{2} + x_{3} = 2 ]

    [-x_{1}+x_{2} + x_{4} = 3 ]

    • 写出系数矩阵:

    [egin{bmatrix} 2 & 1 & 1 & 0 \ -1 & 1 & 0 & 1 end{bmatrix} ]

    • 把初始解搞出来

    [left{egin{matrix} x_{1}=0 \ x_{2}=0 \ x_{3}=2 \ x_{4}=3 end{matrix} ight. ]

    • 然后我们发现,既然现在的基变量的系数全是0,我们可以变相的认为改变基变量不会对C产生影响,为什么我们不从C的表达式中选一个系数大于0的变量增大呢?
    • 现在有这样一些等式

    [x_{3} = 2 - 2x_{1} - x_{2} ]

    [x_{4} = 3 + x_{1} + x_{2} ]

    • 如果我们选择 $ x_{1} $ 并增大,那么 $ x_{3} $ 与 $ x_{4} $ 也会相应的变化,对 $ x_{1} $ 变化的幅度产生限制,如果 $ x_{1} $ 在允许的范围内尽可能变大,那么最终会导致 $ x_{3} $ 与 $ x_{4} $ 中的一个变成0
    • 那么如果 $ x_{1} $ 的变化没有限制呢?那答案就是正无穷了
    • 现在,我们发现由于 $ x_{3} $ 的限制,$ x_{1} $只能变化到1就会卡住(不然 $ x_{3} $ 就小于0了)
    • 然后我们把所有式子中的 $ x_{1} $ 通过 $ x_{3} = 2 - 2x_{1} - x_{2} $ 给取代掉
    • 首先把 $ x_{1} $ 的系数消成1,变形成

    [x_{1} = 1 - frac{1}{2}x_{3} - frac{1}{2}x_{2} ]

    • 然后把所有的 $ x_{1} $ 换掉

    [C = 1 - frac{1}{2}x_{3} - frac{3}{2} x_{2} ]

    [x_{1} = 1 - frac{1}{2}x_{3} - frac{1}{2}x_{2} ]

    [x_{4} = 4 - frac{1}{2}x_{3} + frac{1}{2}x_{2} ]

    • 现在的解是:

    [left{egin{matrix} x_{1}=1 \ x_{2}=0 \ x_{3}=0 \ x_{4}=4 end{matrix} ight. ]

    • 可以发现,现在的状况和刚才是一样的:所有非基变量都是0,所有基变量在C中的系数都是0,基变量的系数构成一个I,但是C的常数项变大了,我们可以一直这么做下去,直到所有的C中变量系数都小于0为止
    • 这个就是单纯形法

    0x06 上代码!##

    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <algorithm>
    #include <string>
    #include <cstring>
    #include <cmath>
    #include <map>
    #include <stack>
    #include <set>
    #include <vector>
    #include <queue>
    #include <time.h>
    #define eps 1e-10
    #define INF 1e15
    #define MOD 1000000007
    #define rep0(j,n) for(int j=0;j<n;++j)
    #define rep1(j,n) for(int j=1;j<=n;++j)
    #define pb push_back
    #define set0(n) memset(n,0,sizeof(n))
    #define ll long long
    #define OJ UOJ
    using namespace std;
    int read() {
    	char c = getchar(); int f = 1, x = 0;
    	while (!isdigit(c)) { if (c == '-') f = -1; c = getchar(); }
    	while (isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }
    	return f*x;
    }
    char get_ch() {
    	char c = getchar();
    	while (!isalpha(c)) c = getchar();
    	return c;
    }
    const int MAXINT = 25;
    int n, m, t;
    struct Linear_Programming {
    	double a[MAXINT][MAXINT]; //m*n的矩阵,[1,1]到[m,n]是系数矩阵,[0,1]到[0,n]是c,[1,0]到[m,0]是b,[0,0]是目标函数值
    	int id[MAXINT * 2];
    	double ans[MAXINT];//1~n是非基变量,n+1~n+m是基变量
    	void pivot(int out, int in) {
    		swap(id[n + out], id[in]);//把非基变量丢进去,基变量丢出来
    		double tmp = a[out][in];
    		a[out][in] = 1;//把基变量那一列换出来
    		for (int i = 0; i <= n; i++) a[out][i] /= tmp;//把需要换进去的变量系数消成1
    		for (int i = 0; i <= m; i++) if (i != out && fabs(a[i][in]) > eps) {//处理系数矩阵,消元,事实上把c和目标函数一起处理了
    			tmp = a[i][in]; a[i][in] = 0;
    			for (int j = 0; j <= n; j++) a[i][j] -= tmp*a[out][j];
    		}
    	}
    	bool init_solution() {
    		rep1(i, n) id[i] = i;
    		while (1) {
    			int in = 0, out = 0;
    			for (int i = 1; i <= m; i++) if (a[i][0] < -eps && (!out || (rand() & 1)))out = i; //初始解不合法,随机选一个不合法的基变量换出来
    			if (!out) break; //合法
    			for (int i = 1; i <= n; i++) if (a[out][i] < -eps && (!in || (rand() & 1))) in = i;//把一个负系数的变量换进去来尝试使初始解合法
    			if (!in) {//没有办法使初始解合法了
    				printf("Infeasible
    ");
    				return false;
    			}
    			pivot(out, in);
    		}
    		return true;
    	}
    	bool simplex() {//求解单纯形
    		while (1) {
    			int in = 0, out = 0;
    			double mn = INF;
    			for (int i = 1; i <= n; i++) if (a[0][i] > eps) { in = i; break; }//选一个系数大于0的丢进去
    			if (!in) break;//没有系数大于0的,已经达到最优解
    			for (int i = 1; i <= m; i++) 
    				if (a[i][in] > eps && a[i][0] / a[i][in] < mn) {
    					mn = a[i][0] / a[i][in]; out = i;
    				}
    			if (!out) {//没有任何限制,无界解
    				printf("Unbounded
    ");
    				return false;
    			}
    			pivot(out, in);
    		}
    		return true;
    	}
    	void work() {
    		if (init_solution() && simplex()) {
    			printf("%.8lf
    ", -a[0][0]);//注意一下算出来的解是真正答案的相反数
    			if (t == 1) {
    				rep1(i, m) ans[id[n + i]] = a[i][0];
    				rep1(i, n) printf("%.8lf ", ans[i]);
    				putchar('
    ');
    			}
    		}
    	}
    }lp;
    int main() {
    	srand(559320414);
    	n = read(); m = read(); t = read();
    	rep1(i, n) lp.a[0][i] = read();
    	rep1(i, m) {
    		rep1(j, n) {
    			lp.a[i][j] = read();
    		}
    		lp.a[i][0] = read();
    	}
    	lp.work();
    	return 0;
    }
    //uoj的模板题
    //事实上只需要记录非基变量,反正编号为i的基变量的系数向量除了i这个位置是1其他都是0,c[i]肯定为0,根本不用记录
    //求解的时候要统一成sigma axi <= b的形式,而且是最大化目标函数,可以用取反或者对偶原理解决
    //对偶原理:把c向量和b向量互换,a矩阵转置(如果像本程序用一个大矩阵记录的话就是把这整个矩阵转置),最大化变成最小化(最小化变成最大化),最终的答案不变
    
    
    • 这里使用了一些trick,a[1][1]开始的矩阵记录的才是系数急诊,a[0][i]记录的是C中的系数,a[i][0]记录的是b,a[0][0]记录的是C的相反数

    0x07 大杀器

    • 随机初始解:如果初始有一些b小于0,随机选个负系数变量交换进去
    • 对偶原理:把整个系数矩阵转置,约束和C中的系数互换,最大化C与最小化C互换,答案不变
    • 具体原因不知

    0x08 什么时候能用这玩意?

    • 一般只能做模板题
    • 能做所有网络流题
    • 不能做要求 $ x_{i} $ 是整数的题,除非系数矩阵是全幺模矩阵(网络流题的矩阵都是全幺模矩阵)
  • 相关阅读:
    PHP5.5新特性
    并发导论【转】
    Redis常用数据结构和操作
    git常用命令【转】
    curl 的用法指南
    pycurl模块
    单点登陆
    MySql 里的IFNULL、NULLIF和ISNULL用法
    Mysql coalesce()函数认识和用法
    python文件操作
  • 原文地址:https://www.cnblogs.com/LoveYayoi/p/6781907.html
Copyright © 2020-2023  润新知