线性规划总结
和网络流有密切联系。网络流是线性规划的特殊形式。
一些概念
定义关于变量 \(x_1,x_2,\cdots,x_n\) 的线性函数 \(f(x_1,x_2,\cdots,x_n)=\sum\limits_{i=1}^na_ix_i\)。关于 \(f\) 的等式和不等式称为线性约束。线性规划就是在一组线性约束下最优化目标线性函数值的方法。
标准型
写成线性代数形式,设 \(\mathrm x\) 为 \(x_i\) 组成的列向量,\(\mathrm c\) 为 \(c_i\) 组成的列向量,\(\mathrm b\) 为 \(b_i\) 组成的列向量,\(A=(a_{ij})_{m\times n}\)。
(写得不太规范,差不多能懂就行。线代形式是为了后面方便对偶的。)
松弛型
添加辅助变量,将 \(\le\) 改为 \(=\)。
目标最小值,可以乘 \(-1\) 转最大值。约束中是 \(\ge\),也可以乘 \(-1\) 转 \(\le\)。如果一个变量范围没有限制,那么可以拆 \(x=x'-x''\),使得 \(x',x''\ge 0\)。
约束中只能是 \(\le\),如果有 \(<\) 是做不了的。
几何意义
有几个可以证明的命题:
-
可行域是凸的。
-
最优解在顶点上。
单纯形法就是搜索每个顶点并且剪枝,最终得到最优解。一次 pivot 操作就是移动到下一个顶点。
单纯形法
分三步:
-
对于松弛型,找到一个基本可行解。
-
进行旋转(pivot)操作来得到更优的解。
-
不断重复 2.
找到基本可行解
观察松弛型,
称第一种约束左边的变量为基本变量,右边的为非基本变量。
如果所有 \(b_i\ge 0\),那么显然所有非基本变量取 \(0\),基本变量取 \(b_i\) 就是一组可行解。
如果存在 \(b_i<0\) 呢?上面的方法不行。有两种方法:创建一个辅助线性规划,或者 OI 中更常用的随机选择法。
随机找到一个 \(b_i<0\)。如果没有,则已经有基本可行解。
在这第 \(i\) 条限制中,我们随机找到一个 \(j\) 使得 \(a_{ij}<0\)。将这个 \(x_j\) 换成基本变量(类似移项)即可使 \(b_i'>0\)。如果找不到,说明没有基本可行解。这里将非基本变量换成基本变量,将基本变量换成非基本变量的操作就是 pivot 操作。
代码
void init(){
while(true){
int e=0,l=0;
for(int i=1;i<=m;++i) if(a[i][0]<-eps&&((!l)||(rand()&1))) l=i;
if(!l) break;
for(int j=1;j<=n;++j) if(a[l][j]<-eps&&((!e)||(rand()&1))) e=j;
if(!e){
cout<<"Infeasible"<<'\n';exit(0);
}
pivot(l,e);
}
}
pivot
类似高消。这里我们简化了矩阵。矩阵中只存了非基本变量前面的系数。因为基本变量前面的系数非 \(0\) 即 \(1\)。为了方便,我们把 \(b\) 塞到第 \(0\) 列。
每次交换前,先要将选中的这一行除以选择的非基本变量前的系数。可以直接存下来这个系数,然后把基本变量的系数交换过来(即 \(1\))。
这一行处理好后,再去更新其他行,类似高消。用其他行 \(i\) 减去选中的这一行 \(j\)。已经交换了基本变量,那一位上的系数应当变成 \(0\)。而又因为原来的非基本变量 \(e\) 的系数是 \(1\),所以其他行 \(i\) 就应当减去对应变量前的系数 \(a_{ie}\) 倍的第 \(j\) 行。
而目标函数是由非基本变量描述的,这里同时更新一下系数。不妨把这个塞在矩阵的第 \(0\) 行,常数项塞在第 \(0\) 列。最终的答案就是矩阵中 \(-a_{00}\)。因为这实际上描述的是 \(-z=a_{00}-\sum\limits_{i=1}^nc_ix_i\)。
代码
void pivot(int l,int e){
swap(id[l+n],id[e]);//id 存第 i 个基本变量的原本编号,用于输出方案,最终方案中每个基本变量取 a[i][0],非基本变量取 0
ld tmp=a[l][e];a[l][e]=1;
for(int j=0;j<=n;++j) a[l][j]/=tmp;
for(int i=0;i<=m;++i){
if(i==l||fabs(a[i][e])<eps) continue;
tmp=a[i][e];a[i][e]=0;
for(int j=0;j<=n;++j) a[i][j]-=tmp*a[l][j];
}
}
如何 pivot 来更新答案?
\(z\) 到最优解时,\(c_i\) 必然都小于等于 \(0\),否则可以增大对应的变量使 \(z\) 更大。这样就可以判最优解。
当还不是最优解时,首先选一个 \(c_e>0\),即选择非基本变量 \(e\)。这表示我们可以增大 \(x_e\) 的值。但是还要保证增大后满足各个约束,一个约束中,\(x_e\) 的最大值为 \(\dfrac{a_{i0}}{a_{ie}}\)。所有约束中应当取最小值。找到这一行,记作 \(l\)。然后就将 \(e\) 与这一行的基本变量交换,即 pivot。
形式化一点。首先找到一个 \(e\) 使得 \(a_{0e}>0\)。如果没有,说明已经是最优解,退出。
再找到一个 \(l\) 使得 \(a_{le}>0\) 且 \(\dfrac{a_{l0}}{a_{le}}\) 最小。如果没有,说明 \(x_e\) 可以无穷大,答案无界。
否则找到了 \(l,e\),进行 pivot。
防止进入循环,遵循 Bland 规则。
-
\(l\) 选约束最紧的。
-
\(e\) 选下标最小的。
代码
void simplex(){
while(true){
int e=0,l=0;ld mi=inf;
for(int i=1;i<=n;++i){
if(a[0][i]>eps){
e=i;break;
}
}
if(!e) break;
for(int i=1;i<=m;++i){
if(a[i][e]>eps&&mi>a[i][0]/a[i][e]){
mi=a[i][0]/a[i][e];l=i;
}
}
if(!l){
cout<<"Unbounded"<<'\n';exit(0);
}
pivot(l,e);
}
}
时间复杂度分析
一次 pivot 是 \(O(nm)\) 的,分析 pivot 的次数,最坏可达指数级(遍历可行域的每个顶点)。可以在一开始进行若干次随机扰动达到期望线性。是有严格多项式复杂度的算法,但没必要学。单纯形法大部分时候表现良好。
代码
void noise(){
for(int i=1;i<=10;++i){
int l=0,e=0;
for(int i=1;i<=m;++i) if(fabs(a[i][0])>eps&&((!l)||(rand()&1))) l=i;
if(!l) break;
for(int i=1;i<=n;++i) if(fabs(a[l][i])>eps&&((!e)||(rand()&1))) e=i;
if(!e) continue;
pivot(l,e);
}
}
对偶定理
对于原问题:
可以对偶为:
就是 \(\min,\max\) 互换,\(A\) 转置,\(\mathrm b,\mathrm c\) 互换,\(\mathrm x\) 改成 \(\mathrm y\),变量 \(\ge 0\) 的约束不变,其他约束 \(\le,\ge\) 互换。
可以证明线性规划的原问题的最优解等于对偶问题的最优解。
不加证明地给出一些结论:
-
互为对偶的两个问题,或者同时有最优解,或者同时都没有最优解。
-
对偶有可行解,原问题不一定有可行解。因为对偶问题的可行解可能是无界解,原问题可能无可行解。
-
原问题有无穷多最优解,只能说明对偶有最优解,不能说明组数。
-
一对问题,若一个有可行解一个无可行解,则有可行解的是无界解。
-
原问题有无界解,对偶问题无可行解。
全幺模矩阵
当一个矩阵的任意一个子方阵的行列式为 \(\pm 1\) 或 \(0\) 时,称之全幺模。
如果单纯形法的矩阵是全幺模的,那么线性规划有整数解。
任何最大流,最小费用最大流的单纯形矩阵都是全幺模的。

浙公网安备 33010602011771号