學習筆記第三十二節:線性規劃與單純形
正題
我們今天講一下線性規劃,以這一道題為例:#179. 線性規劃
首先面對一堆小於等於的約束,我們應該怎麼做?
我們以樣例來解釋:
有四條約束,要同時滿足:
同時,我們要使得最大。
首先,我們可以把可行解在一個二維平面上表示出來。
如圖,可行解就是藍色部分,我們要使得最大,那麼我們就用一條
這就是圖解法。如果有三個變數就是一個立體空間裡面,用一個平面從上往下掃就可以了。但是4個變數的時候就難以構建了。
這就是單純形的應用。
首先我們可以多設兩個變數,把小於等於號去掉。
這個也是沒有錯的,對吧。
我們把答案也寫進去,就變成了。
再變形一下,就變成:
當出現這樣的,常數項是非負的,所有的等式右邊的變數都是一樣的,我們把它叫做基可行解形式。
左邊的變數叫做基變數,右邊的變數叫做非基變數。
在這個時候,我們構造一組答案是極其簡單的,就是讓右邊的變數全部為0,然後左邊的變數就等於右邊的常數項,向上面的方程組,有一個顯然的解就是。
這組解肯定是可行的,而且我們可以保證這組解一定在集合上的多邊形的頂點上。(具體為什麼請先繼續往下看
我們考慮怎麼將答案增大?
我們可以看到,是非基變數,也就是說,他現在取0.
但是他其實可以取到更大,那它到底能取到多大呢?
就要看下面的約束了,對於第一個約束,他最大能取到3(6/2),對於第二個約束,這個約束對是沒有約束的,因為在第二個約束中,不管取到多大,約束都是成立的。
那麼我們只能選第一個約束。
那如果我們讓為3,其他變數最好的情況就是取0。
所以,我們讓作為基變數,讓這個約束原來的基變數換做為非基變數。
顯然這樣子還不是基可行解形式,因為在兩邊都出現了。
那我們做一遍高斯消元就可以了!(意思就是把帶進其他有的地方。
就變成了
那我們就得到了一個更大的解。
回想一下,我們剛剛乾了些什麼?
首先,我們在答案裡面,找出了一個係數大於0的非基變數,這個非基變數,我們把它叫做這一次操作的換入變數,接著,我們又找到一個對這個換入變數約束最緊的約束。將它與這條約束原來的基變數進行一個交換,我們把這個基變數叫做換出變數,這個交換叫做轉軸。接著,我們用這個式子對每一個式子進行高斯消元。
我們就可以得到一組更大的解。當答案式子中非基變數都小於0了。我們就可以斷定答案無法增大了。
如果我們找到了一個大於0的非基變數,但是我們找不到一個約束這個非基變數的約束(係數大於0)。
那麼這個約束就可以無限增大,那麼就是無最大解(Unbounded)的情況。
我們已經解決了如果有一個基可行解的時候,怎麼去找到一個更優的解。
如果我們一開始沒有一個基可行解呢?也就是說,如果我們一開始的約束不滿足基可行解形式呢?
就像第二個樣例:
是不滿足基可行解的形式的。
存在一種解法,可以檢驗是否存在一個初始解。這個方法就是給每一條約束的右邊都加上一個人工變數A,其中A大於等於0.
然後我們儘量使A小,也就是說當A=0時,才存在一個初始解。
因為給每一條約束右邊都加上一個人工變數A就相當於,那麼當A=0時,就是原來的約束。
顯然,我們只要把答案設為-A就可以了,因為可以使得-A最大,從而使得A最小。
但是,現在還是不滿足基可行解形式的,怎麼辦呢?
我們選出常數項最小的那一行。將那一行的A轉軸成一個基變數。
像上面,我們選出第二個約束。
現在肯定可以保證所有的約束都滿足基可行解形式了。
用單純形演算法來求出A的最小值(-A的最大值)就可以了。
若A=0,那麼就有一個初始解,我們將原來的答案序列代入,如果存在一個變數是基變數,那麼就先換成非基變數,最後得出第一組解。
若A不等於0,那麼說明不存在第一組解,程式結束。
剩下的再交給單純形演算法就可以了。
這樣的時間複雜度是很優秀的(隨機情況下)。
但是程式碼量大,不容易實現。
有一種十分玄學的隨機做法。
就是對於一個約束,如果常數項小於0,那麼我們找到一個正係數的非基變數,將這個非基變數轉軸,常數項就大於0了。
如果找不到正係數的非基變數,那麼說明就不存在一個解,因為。
那麼就很好的完美的解決這類的約束最值問題。
程式碼
如果真的要把每一條約束的左右兩邊的存下來,那無疑是非常麻煩的。
其實每一條約束都可以寫成 其中S是一個常數。
那麼我們就可以寫成一個矩陣的形式。
每一行的基變數用一個數組存下來就可以了。
當我們要找一個換入變數的時候,我們還是在第0行找一個正係數的非基變數x。
但是在找一個換出變數的時候,我們要找一行,使得這一行的非基變數x大於0,而不是小於0的。因為相當於一個移項。
轉軸,就相當於給這一行都除以這個非基變數。再給其他行做高斯消元。
注意,這個時候,第0行(答案行)的常數項是答案的相反數。
因為每一次轉軸和高斯消元的過程,就變成了給答案減去一個數。
另外,我們注意到基變數的係數肯定為1,而非基變數只有n個,我們只要每行開n個空間,存下非基變數的係數就可以了。
id表示第i個非基變數存的是幾號變數,id[n+i]表示第i行的基變數是幾號變數。
uoj資料太噁心。97分過不了(其實沒有人過得了
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int n,m,t;
double a[30][25];
double eps=1e-5;
int id[45];
double ans[25];
void pivot(int x,int y){
swap(id[n+x],id[y]);
double temp=a[x][y];
for(int i=0;i<=n;i++) a[x][i]/=temp;
a[x][y]=1/temp;
for(int i=0;i<=m;i++) if(x!=i){
temp=a[i][y];a[i][y]=0;
for(int j=0;j<=n;j++) a[i][j]-=temp*a[x][j];
}
}
bool prepare(){
int x,y;
while(1){
x=y=0;
for(int i=1;i<=m;i++) if(a[i][0]<-eps) {x=i;if(rand()%3!=0) break;}
if(!x) break;
for(int i=1;i<=n;i++) if(a[x][i]<-eps) {y=i;if(rand()%3!=0)break;}
if(!y){
printf("Infeasible");
return false;
}
pivot(x,y);
}
return true;
}
bool simplex(){
int x,y;
double mmin;
while(1){
x=y=0;
for(int i=1;i<=n;i++) if(a[0][i]>eps) {y=i;break;}
if(!y) break;
mmin=(double)1e18;
for(int i=1;i<=m;i++) if(a[i][y]>eps && a[i][0]/a[i][y]<mmin) {x=i;mmin=a[i][0]/a[i][y];}
if(!x) {
printf("Unbounded");
return false;
}
pivot(x,y);
}
return true;
}
int main(){
srand(23333);
scanf("%d %d %d",&n,&m,&t);
for(int i=1;i<=n;i++) scanf("%lf",&a[0][i]);
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++) scanf("%lf",&a[i][j]);
scanf("%lf",&a[i][0]);
}
for(int i=1;i<=n;i++) id[i]=i;
if(prepare() && simplex()){
printf("%.8lf\n",-a[0][0]);
if(t){
for(int i=1;i<=m;i++) if(id[n+i]>=1)ans[id[n+i]]=a[i][0];
for(int i=1;i<=n;i++) printf("%.8lf ",ans[i]);
}
}
}
關於對偶:
前面我們研究的問題都是“約束為小於等於,求最大值的問題”,然而現在要求解得問題是"約束為大於等於,求最小值的問題"。
首先符號不是問題,我們只要將兩邊同時乘上-1,就可以變號了。
最小值也不是問題,那我們在單純形演算法的時候,在答案式拿出一個係數為負的就可以了。
但是這樣使得在找初始解的時候可能會變慢許多,所以,有人可以證明上面的兩個問題是一個對偶問題,也就是答案相同。
那麼怎麼個對偶法呢?
對於標準型線性規劃問題:
它的對偶問題是:
相關證明請翻閱演算法導論。
其中A是一個係數矩陣,是A的轉置。表示的是第i行的A。
另外的,對於的任意一個n*n的矩陣行列式都為,那麼這個線性規劃問題的最優解是整數。當然,還要求都為整數矩陣。