【BZOJ3265】志願者招募加強版 線性規劃 單純形法 對偶原理

鏈接:

#include <stdio.h>
int main()
{
    puts("轉載請註明出處[vmurder]謝謝");
    puts("網址:blog.csdn.net/vmurder/article/details/45673079");
}

題解:

這道題是線性規劃求目標函數最小值 ,對偶原理轉一下就成了單純形算法求線性規劃最大值。

單純形法:

首先這篇博客缺失了很多證明,只能講述單純形法的實現。

 // N個變量 M個限制
double a[M][N]; // 這m個限制裏變量的係數
double b[M]; // 第i個限制加和 <=bi
double c[N]; // 目標函數裏變量係數
double ans; // 目標函數自帶的係數
void pivot(int x,int y); // 轉軸操作
void solve();

首先對於如下數據

3 3 

2 3 4

1 1 2 2

1 2 3 5

1 3 3 2

我們可以得到以下的限制:
目標函數: 2x1+5x2+2x3+0
限制函數:
x12
x1+x23
x2+x34

然後若 x4x5x6 均爲任意值。
那麼我們可以將式子表示爲
狀態 1: **************
目標函數: 2x1+5x2+2x3+0
限制函數:
x4+1x1=2
x5+1x1+1x2=3
x6+1x2+1x3=4

然後在單純形的過程中函數們的各系數得到改變的過程如下:

狀態 1: **************
目標函數: 2x1+5x2+2x3+0
限制函數:
x4+1x1=2
x5+1x1+1x2=3
x6+1x2+1x3=4
狀態 2: **************
目標函數: 2x4+5x2+2x3+4
限制函數:
x1+1x4=2
x5+1x4+1x2=1
x6+1x2+1x3=4
狀態 3: **************
目標函數: 3x4+5x5+2x3+9
限制函數:
x1+1x4=2
x2+1x4+1x5=1
x6+1x4+1x5+1x3=3
狀態 4: **************
目標函數: 3x1+5x5+2x3+15
限制函數:
x4+1x1=2
x2+1x1+1x5=3
x6+1x1+1x5+1x3=1
狀態 5: **************
目標函數: 1x1+3x5+2x6+17
限制函數:
x4+1x1=2
x2+1x1+1x5=3
x3+1x1+1x5+1x6=1

線性規劃最終答案:17

單純形思想:

其實就是每次找一個在目標函數中存在的變量,跟不在其中的某個變量交換一下,然後換來換去最終目標函數中所有變量係數都爲負了,那麼目標函數最後加的常數就是答案辣。

單純形實現:

具體的實現過程呢?

struct Simplex
{
    int n,m; // n個變量、m個限制
    double a[M][N],b[M],c[M],ans;
    void pivot(int x,int y)
    {
        int i,j;
        double t;
        b[x]/=a[x][y];
        for(i=1;i<=n;i++)if(i!=y)a[x][i]/=a[x][y];
        a[x][y]=1.0/a[x][y];

        for(i=1;i<=m;i++)if(i!=x&&fabs(a[i][y])>eps)
        {
            b[i]-=a[i][y]*b[x],t=a[i][y];
            for(j=1;j<=n;j++)a[i][j]-=t*a[x][j];
            a[i][y]=-t*a[x][y];
        }

        ans+=c[y]*b[x],t=c[y];
        for(i=1;i<=n;i++)c[i]-=t*a[x][i];
        c[y]=-t*a[x][y];
    }
    double solve()
    {
        read();
        double t;
        for(int i,x,y;;)
        {
            for(i=1;i<=m;i++)if(c[i]>eps){y=i;break;}
            if(i>m)return ans;

            for(t=inf,i=1;i<=m;i++)
                if(a[i][y]>eps&&t>b[i]/a[i][y])
                    x=i,t=b[i]/a[i][y];
            if(t==inf)return t;
            else pivot(x,y);
        }
    }
}simplex;

下面的實現裏存在兩個坑,請不要深究它們怎麼證。

double solve() :

首先我們每次找一個標號最靠前的【坑1】目標函數中係數爲正的變量 y (如第30行),然後如果找不到,就表示已經找到了最優解【坑2】。

然後我們找這個變量在哪個限制裏是”最緊”的【坑3】,即33、34、35這三行。
如果找不到任何一個”緊”的行,即 a(i,y)<=eps 那麼這個變量想多大就能多大,反正有輔助變量頂着,可以直接返回無界【坑4】,即答案是無窮大。

然後我們對此限制中的輔助變量和找到的 y 進行轉軸操作。

void pivot(int x,int y) :

先把第 x 個限制的係數改一改,然後用它去消其它的限制,再消一下目標函數。結束。類似高斯消元?

對偶原理:

如果要求最小值,那麼我們把模型對偶一下就可以辣。【坑5】

即限制矩陣 【轉置】 一下,然後目標函數的係數和每個限制的最終 的那個值也互換一下。

【轉置】 :矩陣的元素 a(i,j) 變爲 a(j,i)

對於證明坑的總結:

【坑1】爲什麼要找標號最小的?
根據Bland法則,這麼做可以避免被卡死循環。
【坑2】爲什麼標號都是負的就表示找到了最優解?
表示無法理解爲什麼不能給某個存在負係數的變量轉一轉。
【坑3】爲什麼要找”最緊”的?
不知道不知道。
【坑4】爲什麼直接返回無界了?
不可以別的某個變量再來轉一轉,然後就迴歸有界?
【坑5】對偶原理是毛線?
天然坑,深坑。

代碼:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 1010 // 變量個數
#define M 10100 // 限制個數
#define inf 1e9
#define eps 1e-5
using namespace std;
struct Simplex
{
    int n,m; // n個變量、m個限制
    double a[M][N],b[M],c[M],ans;
    void read()
    {
        int i,j,k,l,r;
        scanf("%d%d",&m,&n);
        for(i=1;i<=m;i++)scanf("%lf",&c[i]);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&k);
            while(k--)
            {
                scanf("%d%d",&l,&r);
                for(j=l;j<=r;j++)a[i][j]=1.0;
            }
            scanf("%lf",&b[i]);
        }
        swap(n,m);
    }
    void pivot(int x,int y)
    {
        int i,j;
        double t;
        b[x]/=a[x][y];
        for(i=1;i<=n;i++)if(i!=y)a[x][i]/=a[x][y];
        a[x][y]=1.0/a[x][y];

        for(i=1;i<=m;i++)if(i!=x&&fabs(a[i][y])>eps)
        {
            b[i]-=a[i][y]*b[x],t=a[i][y];
            for(j=1;j<=n;j++)a[i][j]-=t*a[x][j];
            a[i][y]=-t*a[x][y];
        }

        ans+=c[y]*b[x],t=c[y];
        for(i=1;i<=n;i++)c[i]-=t*a[x][i];
        c[y]=-t*a[x][y];
    }
    double solve()
    {
        read();
        double t;
        for(int i,x,y;;)
        {
            for(i=1;i<=m;i++)if(c[i]>eps){y=i;break;}
            if(i>m)return ans;

            for(t=inf,i=1;i<=m;i++)
                if(a[i][y]>eps&&t>b[i]/a[i][y])
                    x=i,t=b[i]/a[i][y];
            if(t==inf)return t;
            else pivot(x,y);
        }
    }
}simplex;
int main()
{
//  freopen("test.in","r",stdin);
    printf("%d\n",(int)(simplex.solve()+eps));
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章