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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章