1. 程式人生 > 實用技巧 >單純形法與線性規劃入門

單純形法與線性規劃入門

前言

說實話其實這個演算法真的不難,只要你有耐心把它啃完。。。

線性規劃

線性規劃的標準型長這個樣子:

\[\begin{align}max\ \ &\sum_{j=1}^nc_jx_j&\\s.t.\ \ &\sum_{j=1}^na_{i,j}x_j\le b_i&i=1,2,...,m\\&x_j\ge0&j=1,2,...,n\end{align} \]

它的鬆弛型則是長這個樣子:

\[\begin{align}max\ \ &\sum_{j=1}^nc_jx_j&\\s.t.\ \ &x_{n+i}=b_i-\sum_{j=1}^na_{i,j}x_j&i=1,2,...,m\\&x_j\ge0&j=1,2,...,n+m\end{align} \]

考慮和原本的標準型相比,鬆弛型有什麼優勢。

容易發現,原本限制條件中的不等號,被我們轉化成了等號。

這一步轉化是之後所有操作的基礎。

\(Pivot\)(轉軸)

考慮到我們需要最大化的式子\(\sum_{j=1}^nc_jx_j\)

如果我們能夠通過某些手段把\(x\)的係數全部都變成負數,那麼因為\(x_j\ge0\),只要令\(x_{1\sim n}=0\)即可得到最大值。

但我們該怎樣把\(x\)的係數都變成負數呢?

其實,只要通過等量代換就可以了。

考慮\(x_j\ge 0\)並不是僅針對\(x_{1\sim n}\),而同時要求\(x_{n+1\sim n+m}\)也滿足這個條件。

而它們之間存在一定的等量關係,就可以試著通過等量代換來調整所需最大化的式子。

我們規定把\(x_e\)\(x_{n+l}\)代換的過程叫做\(Pivot(l,e)\)

已知:

\[x_{n+l}=b_l-\sum_{j=1}^na_{l,j}x_j \]

我們移出右式中的\(x_e\),將它和\(x_{n+l}\)調換位置得到:

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

然後我們再把其餘式子中的\(x_e\)全部用右式代換,就徹底抹消了\(x_e\)的存在,完成了一次轉軸。

\(Init\)(初始化)

考慮如果存在某個\(b_i<0\)

,我們就無法令\(x_{1\sim n}=0\),因為這樣就不滿足限制了。

仔細觀察轉軸操作,可發現這一操作過程中並不會改變\(b_i\)的正負性,因此只要我們在一開始把所有\(b_i\)都強制修改為正數即可。

為了達成這一目標,每次我們找出一個\(b_l<0\),並找到一個\(a_{l,e}<0\),然後只要執行\(Pivot(l,e)\)即可。

如果找不到,顯然無解。

\(Simplex\)(單純形法)

單純形法主過程的核心問題,就是每次應該對哪一對\(l,e\)執行轉軸操作:

  • 首先,需要滿足\(c_e>0\),且轉軸後可以讓新的\(c_e<0\)(這是我們執行轉軸操作的初衷)。

  • 其次,在所有可選的\(l\)中,我們要使\(\frac{b_l}{a_{l,e}}\)最小(要選擇最緊的限制)。

只要不斷進行\(Pivot(l,e)\),直到無法繼續就可以了。

程式碼

【UOJ179】線性規劃(據說因為出題人卡常卡精度,只要拿到\(97\)分就可以了?)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 20
#define DB double
#define eps 1e-8
using namespace std;
int n,m,op;DB a[N+5][N+5];
namespace SimplexMethod//單純形法
{
	int id[2*N+5];DB s[N+5];
	I void Pivot(CI l,CI e)//轉軸
	{
		RI i,j;DB t=a[l][e];swap(id[n+l],id[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];//等量代換
	}
	I bool Init()//初始化,使所有b[i]為正
	{
		RI i,l,e;W(1)//不斷操作
		{
			for(l=e=0,i=1;i<=m;++i) a[i][0]<-eps&&(!l||rand()&1)&&(l=i);if(!l) break;//找到b[l]<0
			for(i=1;i<=n;++i) a[l][i]<-eps&&(!e||rand()&1)&&(e=i);//找到a[l][e]<0
			if(!e) return puts("Infeasible"),0;Pivot(l,e);//找不到則無解,否則轉軸
		}return 1;
	}
	I bool Simplex()//單純形法主過程
	{
		RI i,l,e;DB Mn;W(1)//不斷操作
		{
			for(l=e=0,Mn=1e9,i=1;i<=n;++i) if(a[0][i]>eps) {e=i;break;}if(!e) break;//找到c[e]>0
			for(i=1;i<=m;++i) a[i][e]>eps&&a[i][0]/a[i][e]<Mn&&(Mn=a[i][0]/a[i][e],l=i);//找到限制最緊的l
			if(!l) return puts("Unbounded"),0;Pivot(l,e);//如果所有a[l][e]<0無窮大,否則轉軸
		}return 1;
	}
	I void Solve()//單純形法
	{
		RI i;for(srand(20050521),i=1;i<=n;++i) id[i]=i;if(Init()&&Simplex())
		{
			if(printf("%.10lf\n",-a[0][0]),!op) return;//輸出答案
			for(i=1;i<=m;++i) s[id[n+i]]=a[i][0];for(i=1;i<=n;++i) printf("%.10lf ",s[i]);//輸出方案
		}
	}
}
int main()
{
	RI i,j;for(scanf("%d%d%d",&n,&m,&op),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]);}//讀入
	return SimplexMethod::Solve(),0;//單純形法
}