HYSBZ - 1061 志愿者招募 (单纯形法模板 | 对偶问题转化)

版权声明:Why is everything so heavy? https://blog.csdn.net/lzc504603913/article/details/83032755

1061: [Noi2008]志愿者招募

Time Limit: 20 Sec  Memory Limit: 162 MB
Submit: 5889  Solved: 3545
[Submit][Status][Discuss]

Description

  申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管。布布刚上任就遇到了一个难

题:为即将启动的奥运新项目招募一批短期志愿者。经过估算,这个项目需要N 天才能完成,其中第i 天至少需要

Ai 个人。 布布通过了解得知,一共有M 类志愿者可以招募。其中第i 类可以从第Si 天工作到第Ti 天,招募费用

是每人Ci 元。新官上任三把火,为了出色地完成自己的工作,布布希望用尽量少的费用招募足够的志愿者,但这

并不是他的特长!于是布布找到了你,希望你帮他设计一种最优的招募方案。

Input

  第一行包含两个整数N, M,表示完成项目的天数和可以招募的志愿者的种类。 接下来的一行中包含N 个非负

整数,表示每天至少需要的志愿者人数。 接下来的M 行中每行包含三个整数Si, Ti, Ci,含义如上文所述。为了

方便起见,我们可以认为每类志愿者的数量都是无限多的。

Output

  仅包含一个整数,表示你所设计的最优方案的总费用。

Sample Input

3 3
2 3 4
1 2 2
2 3 5
3 3 2

Sample Output

14

HINT

1 ≤ N ≤ 1000,1 ≤ M ≤ 10000,题目中其他所涉及的数据均 不超过2^31-1。

之前用费用流做过一次,主要是利用等式根据流量平衡建模,加上费用跑费用流来解

这次使用单纯形去解决,同时学习单纯形模板和如何转换对偶问题!

我们用 B[i] 代表第i天需要的人数

A[i][j]代表第i天,第j个人能否工作(取值为0或1)

X[i]代表使用了第i个人的个数 

C[i]代表第i个人的花费

那么我们就能得到如下不等式:(假设有两天三个人)

A_{11}X_{1} + A_{12}X_{2}+A_{13}X_{3} \geqslant B_{1}

A_{21}X_{1} + A_{22}X_{2}+A_{23}X_{3} \geqslant B_{2}

我们的目标函数是:

最小化 : C_{1}X_{1}+C_{2}X_{2}+C_{3}X_{3}

我们要把这个最小化问题转化为最大化问题,也就是把这个问题转化成他的对偶问题才能用单纯形法去解决!具体过程如下:

因为目标函数的系数都比A大,所以我们可以有如下不等式

C_{1}X_{1}+C_{2}X_{2}+C_{3}X_{3} \geqslant A_{11}X_{1} + A_{12}X_{2}+A_{13}X_{3} \geqslant B_{1}

更一般的,我们引入变量 y[i]代表第i个方程的系数,得到如下的方程

C_{1}X_{1}+C_{2}X_{2}+C_{3}X_{3} \geqslant y_{1}(A_{11}X_{1} + A_{12}X_{2}+A_{13}X_{3} ) + y_{2}(A_{21}X_{1} + A_{22}X_{2}+A_{23}X_{3})\geqslant y_{1}B_{1} + y_{2}B_{2}

我们化简一下,变成

C_{1}X_{1}+C_{2}X_{2}+C_{3}X_{3} \geqslant (y_{1}A_{11}+ y_{2}A_{21})X_{1} + (y_{1}A_{12}+y_{2}A_{22})X_{2}+(y_{1}A_{13}+y_{2}A_{23})X_{3} \geqslant y_{1}B_{1} + y_{2}B_{2}

那么我们的方程就变为已知:

y_{1}A_{11}+ y_{2}A_{21} \leqslant C_{1}

y_{1}A_{12}+y_{2}A_{22} \leqslant C_{2}

y_{1}A_{13}+y_{2}A_{23} \leqslant C_{3}

最大化目标函数:

y_{1}B_{1} + y_{2}B_{2}

这样子我们就成功的转化为了对偶问题了,至于为什么可以这样转换,往上一大堆资料,这里只阐述过程~

注意!这里的X都要满足>=0的约束!才可以转换!!!

然后就可以简单粗暴的上单纯形模板了~!

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1500;
const int MAXM=15000;
const double INF=1e10;
const double eps=1e-7;
typedef long long ll;

int N,M;
double A[MAXM][MAXN];//A[i][j]表示第i个方程,Xj的系数是多少
double c[MAXN];//第i个方程需要<=的常数
double b[MAXM];//目标方程Xj的系数是多少
double v;

void Pivot(int l,int e)
{
    b[l]/=A[l][e];
    for(int i=1;i<=N;i++)
        if(i!=e)
            A[l][i]/=A[l][e];
    A[l][e]=1/A[l][e];
    for(int i=1;i<=M;i++)
        if(i!=l && fabs(A[i][e])>eps)
        {
            b[i]-=A[i][e]*b[l];
            for(int j=1;j<=N;j++)
                if(j!=e)
                    A[i][j]-=A[i][e]*A[l][j];
            A[i][e]=-A[i][e]*A[l][e];
        }

    v+=c[e]*b[l];
    for(int i=1;i<=N;i++)
        if(i!=e)
            c[i]-=c[e]*A[l][i];
    c[e]=-c[e]*A[l][e];
}

double Simplex()
{
    int i,l,e;
    while(true)
    {
        for(i=1;i<=N;i++)
            if(c[i]>eps) 
                break;
        if((e=i)==N+1) 
            return v;//答案
        double tmp=INF;
        for(i=1;i<=M;i++)
            if(A[i][e]>eps && b[i]/A[i][e]<tmp)
                tmp=b[i]/A[i][e],l=i;
        if(tmp==INF)
            return INF;//无界
        Pivot(l,e);
    }
}
int main()
{
    scanf("%d%d",&N,&M);
    for(int i=1;i<=N;i++)
        scanf("%lf",&c[i]);
    int x,y;
    for(int i=1;i<=M;i++){
        scanf("%d%d%lf",&x,&y,&b[i]);
        for(int j=x;j<=y;j++)
            A[i][j]=1;
    }
    
    double ans=Simplex();
    printf("%d\n",int(ans+0.5));
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lzc504603913/article/details/83032755