版权声明:欢迎转载(请附带原链接)ヾ(๑╹◡╹)ノ" https://blog.csdn.net/corsica6/article/details/84565770
线性规划
线性规划问题要求最大化或最小化一个受限于一组有限的线性约束的线性函数。
具体表现为:
已知一组实数
a1,a2,..,an,以及一组变量
x1,x2,..,xn,设在这些变量上的一个线性函数为
f(x1,x2,..,xn)=i=1∑naixi。
给定
b,
f(x1,x2,..,xn)≤b,=b或
≥b统称为线性约束。
称满足所有限制条件的解
x1,x2,..,xn为可行解,使目标函数达到最优的可行解为最优解,所有可行解构成的区域为解空间。
标准型
最大化
i=1∑ncixi
满足约束
j=1∑nai,jxj≤bii=1,2,..,m
xj≥0j=1,2,..,n
所有的线性规划问题经过变换后都可以用标准型来表示。
松弛型
最大化
i=1∑ncixi
满足约束
xi+n=bi−j=1∑nai,jxji=1,2,..,m
xj≥0j=1,2,..,n
网络流模型
设
f(u,v)表示
(u,v)的流量,
c(u,v)表示流量上界,
w(u,v)表示费用。
最大流:
从汇点
t向
s连容量为
+∞的边,满足流量守恒之后,求最大流量。
最大化
f(s,t)
满足约束
f(u,v)≤c(u,v)(u,v)∈E
u∑f(u,v)=u∑f(v,u)v∈V
f(u,v)≥0(u,v)∈E∪{(t,s)}
最小费用流:
从汇点
t向
s连容量为
+∞,费用为
0的边,满足流量守恒之后,求最小费用流。
最小化
(u,v)∈E∑w(u,v)f(u,v)
满足约束
f(u,v)≤c(u,v)(u,v)∈E
u∑f(u,v)=u∑f(v,u)v∈V
f(u,v)≥0(u,v)∈E∪{(t,s)}
最小费用最大流:
加上一个约束条件
f(t,s)=maxflow即可。
单纯形
单纯形是指
k维空间中有
k+1个顶点的凸多面体(如三角形,四面体)。
一个线性规划的解空间是所有线性不等式解空间的交。而注意到对于每个线性不等式的解空间都是一个凸形区域(定义:区域内任意两点连线上的点都属于这个区域),可以归纳证明,线性规划的解空间是一个凸形区域。
不考虑最优解无穷大/小的情况,在一个凸形区域上,它的所有局部最优解必然只有一个值(且等于全局最优解的值)。可以类似于爬山算法,贪心向全局最优解趋近。
在这个凸多边形上求解最优值的一个一般性方法就称为单纯形法。
单纯形法
等式问题比较好解决,所以单纯形法求解线性规划时先将其转化为松弛型:
最大化
i=1∑ncixi
满足约束
xi+n=bi−j=1∑nai,jxji=1,2,..,m
xj≥0j=1,2,..,n
其中
x1,x2,..,xn这些在等式右边的变量称为非基变量,
xn+1,xn+2,..,xn+n这些在等式左边的变量称为基变量。
当所有
bi≥0时,所有基变量等于
bi,非基变量等于0就是一组可行解,这样的解称为基本解。单纯形算法中只需要考虑基本解。
基本操作
下面介绍一个单纯形一个重要操作:转轴(pivot)。
pivot(b,n)指将基变量
xB与非基变量
xN互换(称这个
xN为换入变量,
xB为换出变量)。具体而言是将
xN放在原本
xB所在的等式左侧,而用
xB和其他非基变量来替代
xN。
由
xB=bi−j=1∑nai,jxj
得到
xN=ai,Nbi−j=1,j̸=N∑nai,jxj−xB
注意转轴操作需满足:
ai,N̸=0。
另一个重要操作:simplex。
具体的最优化过程如下:
- 选择一个满足
ce>0的非基变量
xe
- 找到满足
al,e>0且
al,ebl最小的基变量
xn+l
- 执行pivot(l,e),然后回到第一步
解释:
论文中的一个具体的例子:
首先把
x1作为换入变量,因为
c1>0,所以增大
x1必然使得解增大,而因为约束条件导致
x1增大必然有个上限,在第三个约束中最紧:
x1≤9,把
x6作为换出变量。
取约束最近的是因为这样代换后能保证其他所有等式中
bi仍然满足
≥0。
不断迭代最终得到一个
ci都
≤0的式子,而
xi≥0导致无法得到更大的解了。
最优解就是当前的基本解。
若存在
ce>0但所有
al,e都
≤0时,最优解可以达到正无穷。
但是基本解和上述过程都是在所有
bi≥0的前提下进行的,如果有
bi<0怎么办呢?
法1(论文中的方法):
我们再多加一个辅助的线性规划:
若最优解中
x0̸=0,则无解。
例如:
找到最小的
bl,把
xl+n作为换出变量执行一次转轴操作(因为
bl<0且
bl最小,所以这样代换后满足所有
bi≥0)。
代入第二项:
最后得到:
那么存在最优解
0。不失一般性,如果最后
x0不是基变量,再转轴操作一次即可(
x0=0,所以
bi=0,因此选择任意转轴即可,除了
aij̸=0以外没有限制)。
然后把
x0代出,换回最初的式子(是基变量的就用其它非基变量表达)后进行
simplex操作即可。
法2(uoj上学到的方法):
这个方法相较上一个方法简捷许多。
我们的目标是让所有
bi≥0。
所以每次选择一个
bi<0的基变量
xn+l,在该约束右边找一个
ai,j<0(即系数为正)的非基变量
xe,进行转轴操作即可。
若在右边所有
ai,j≥0,则无解。
复杂度分析
有一个不会证明的定理:
一般线性规划问题可以在多项式时间内解决。
1s过几百的样子。。。
一次转轴操作复杂度为
O(nm),而最优化(解决
bi<0的情况)操作中转轴操作调用次数可能为指数级,但上界很难卡满。
理论复杂度(???):
法1:
O(n3.5m2)
法2:
O(n3.5m)
代码
实际上并不需要真的把松弛型建出来,具体看代码吧(只需要记录
x1,x2,..,xn的位置即可)。
传送门:uoj179
#include<bits/stdc++.h>
typedef double db;
const db eps=1e-8,inf=1e233;
using namespace std;
const int N=100;
int id[N],tp[N],n,m,t;
db a[N][N];
inline void pivot(int r,int c)
{
swap(id[r+n],id[c]);
int i,j;db res=-a[r][c];a[r][c]=-1.0;
for(i=0;i<=n;++i) a[r][i]/=res;
for(i=0;i<=m;++i) if((i!=r)&&((a[i][c]<-eps)||(a[i][c]>eps))){
res=a[i][c];a[i][c]=0;
for(j=0;j<=n;++j) a[i][j]+=res*a[r][j];
}
}
inline void sol()
{
int i,x,y;db w,res;
for(i=1;i<=n;++i) id[i]=i;
for(;;){
x=y=0;
for(i=1;i<=m;++i)
if((a[i][0]<-eps)&&((!x)||(rand()&1))) x=i;
if(!x) break;
for(i=1;i<=n;++i) if(a[x][i]>eps) {y=i;break;}
if(!y) {printf("Infeasible");return;}
pivot(x,y);
}
for(;;){
x=y=0;
for(i=1;i<=n;++i)
if((a[0][i]>eps)&&((!y)||(rand()&1))) y=i;
if(!y) break;w=inf;
for(i=1;i<=m;++i)
if((a[i][y]<-eps)&&((res=-a[i][0]/a[i][y])<w)) w=res,x=i;
if(!x) {printf("Unbounded");return;}
pivot(x,y);
}
printf("%.9f\n",a[0][0]);
if(!t) return;
for(i=n+1;i<=n+m;++i) tp[id[i]]=i-n;
for(i=1;i<=n;++i) printf("%.9f ",tp[i]?a[tp[i]][0]:0);
}
int main(){
int i,j;srand(time(0));
scanf("%d%d%d",&n,&m,&t);
for(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]);a[i][j]*=-1;}
scanf("%lf",&a[i][0]);
}
sol();
return 0;
}
参考资料