BZOJ1061 單純形法的初探

起因是一道Timus的題目 , 長着一副經典的線性規劃的模樣:

AxBMin(Cx)
然而我嘗試去網絡流建模 , 無果。 當時我就考慮 , 爲什麼一個明顯的線性規劃問題要用網絡流去擬合呢? 爲什麼不就用該用的方法去做呢? 於是我決定學學單純形法。

單純形法本身並不難理解 , 只是有很多名詞可能讓剛開始看的小夥伴有點爲難。這裏推薦一篇論文入門非常不錯。 看到Page12 就可以啦。然後讓我們來看看一道很經典的網絡流建圖的難題:

BZOJ 1061 志願者招募

這裏設志願者數量的向量爲X , 每天需要人數的向量爲Y , 目標最小值的係數向量爲C , 便可以得到一個最小化目標值的線性規劃:

AXYMIN(CX)

但這個線性規劃不好找基本解 , 所以我們嘗試把這玩意對偶:

ATXCMAX(YX)

由於本題我們只求目標值而不用求對應的方案所以 , 我們其實只需要把那個矩陣轉置一下就好啦。 (並不推薦寫網上的某些寫法 , 把一個好好的矩陣拆成三個QAQ)

誒 , 當你正準備用單純形法求這玩意的最大值的時候你會發現 , 這玩意好像我存不下。 因爲當我添加了若干鬆弛變量之後 , 這玩意就是108 級別的了。

當時這裏我糾結了很久 , 但這就是一個很常見的針對稀疏矩陣的優化(弱省的學生學校裏沒有善良的學長啊)。

思路就是有的變量那一個列向量只有一個係數爲1的值(其餘都是0) , 所以我們不存這些東西 , 當我們pivot 的時候我們swap 一下兩個變量的位置就好。

至此 ,此題就可以AC了。

這裏貼一個會MLE但是思路清晰的代碼 , 自己做的時候可以把a ,b 中的某個數組去掉即可

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <deque>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
#include <cassert>

using namespace std;
const int maxn = 1100;
const int maxm = 11000;
const double eps = 1e-7;
const double INF = 1e20;

int dcmp(double a) { return fabs(a)<eps?0:a<0?-1:1; }

int n , m;
double b[maxn][maxm] , a[maxm][maxn];

void dualize()
{
    swap(++n, ++m);
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j] = b[j][i];
}

void pivot(int l , int e)
{
    for(int i=1;i<=n;i++) if(i!=l && dcmp(a[i][e]))
    {
        for(int j=1;j<=m;j++) if(j!=e) a[i][j] -= a[i][e]/a[l][e]*a[l][j];
        a[i][e] /= -a[l][e];
    }

    for(int i=1;i<=m;i++) if(i!=e) a[l][i] /= a[l][e]; a[l][e] = 1/a[l][e];
}

double simplex()
{
    double mn;
    int e , l;
    while(true)
    {
        for(e=1;e<m;e++) if(dcmp(a[n][e]) > 0) break;
        if(e == m) return -a[n][m];

        mn = INF;
        for(int i=1;i<n;i++) if(dcmp(a[i][e]) > 0 && mn > a[i][m]/a[i][e]) mn = a[l=i][m]/a[i][e];
        if(mn == INF) return INF;
        pivot(l, e);
    }
}

int main(int argc, char *argv[]) {

    cin>>n>>m;
    for(int i=1;i<=n;i++) scanf("%lf" , &b[i][m+1]);
    for(int i=1,l,r,v;i<=m;i++) 
    {
        scanf("%d%d%d" , &l,&r,&v);
        for(int j=l;j<=r;j++) b[j][i] = 1;
        b[n+1][i] = v;
    }

    dualize();
    printf("%d" , (int)(simplex()+0.50));

    return 0;
}

一些問題:

Q: 這玩意本來沒次pivot 的運算量就大 , 而且還是指數級別的算法, 爲神馬不超時呢?

A: 數據不好 , 好的話一樣超時……

Q: 爲什麼你不記錄此時的到底是哪些是鬆弛變量啊, 這樣到最後不就不知道哪些變量的對應關係了麼?

A: 是的 , 我確實應該記錄這些變量的對應關係 , 但是此題不要求輸出方案 , 這就免了(博主懶)

Q: 我看到網上的很多單純形的代碼很長的樣子 , 你的代碼這麼短有沒有暗傷啊?

A: 博主巨弱 , 表示目前沒有發現

一個小小的總結:
我後來又嘗試用單純形法解決網絡流的題目 , 正確性和通解的優勢是明顯的 , 因爲通過代數建模比通過圖論構造要靈活很多 , 就想用解析幾何來做幾何題一樣。但是很多題目, 無論是時間還是空間都很緊迫 , 所以此方法在大多時候都不能上“正席” , 但是如果考試的時候一個網絡流題目建模困難 , 而你會單純形法 , 那麼……(自由想象)

發佈了99 篇原創文章 · 獲贊 13 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章