【learning】一種奇妙的網絡流建模方式


吐槽

好吧這個是真的很妙qwq用來解方程組的網絡流嗯不能更清真


正題

首先是大概描述

當一個方程組中所有的方程相加之后可以把所有的變量都消掉(也就是所有變量都出現一正一負可以抵消掉),我們會發現這個其實很像網絡流中的流量平衡(正負一個看做流入一個看做流出)。我們可以將每條式子都看做一個點,方程中的常數就是限制方程成立的容量,這樣我們就可以跑最大流來求出這個方程組的解了(如果說有一些別的限制條件什么滿足×××最小或者最大啊之類的那就跑費用流就好)

這聽起來很玄妙

然而其實。。拿一道題來說事會更加直觀一點。。

bzoj1061 志願者招募

我們設第\(i\)類志願者的人數為\(x_i\),每個志願者的費用為\(v_i\),第\(j\)天雇佣的人數為\(p_j\),那么我們可以把每天的雇佣人數寫成一個不等式,為了方便表述這里給幾個具體的數好一點

天數4,\(p =\){\(2,3,4,5\)},總共有5類志願者如下表

1 2 3 4 5
時間 1-1 1-2 2-3 3-3 3-4
費用 3 4 5 6 7

那么我們可以列出式子:
\[ \begin{aligned} &p_1 = x_1+x_2>=2\\ &p_2=x_1+x_3>=3\\ &p_3=x_3+x_4+x_5>=4\\ &p_4=x_5>=5\\ \end{aligned} \]
為了把不等式轉成等式,我們可以對每個式子添加一個輔助變量\(y_i\),注意\(y_i\)是有范圍的(在不同的題目里面會有不同的限制,但是在這題里面比較直接就是\(y_i>=0\)\(y_i\)的范圍會影響到后面建圖的時候的邊的容量),那么我們可以把式子轉化成這樣:
\[ \begin{aligned} &p_0=0\\ &p_1=x_1+x_2-y_1=2\\ &p_2=x_1+x_3-y_2=3\\ &p_3=x_3+x_4+x_5-y_3=4\\ &p_4=x_5-y_4=5\\ &p_5=0\\ \end{aligned} \]
為什么要在一前一后加多兩條式子呢?因為這樣以后,我們每次用第\(i+1\)條式子減去第\(i\)條式子,就可以得到這樣的東西:
\[ \begin{aligned} &x_1+x_2-y_1=2&(p_1-p_0)\\ &x_3-x_2+y_1-y_2=1&(p_2-p_1)\\ &x_4+x_5-x_1+y_2-y_3=1&(p_3-p_2)\\ &-x_3-x_4+y_3-y_4=1&(p_4-p_3)\\ &-x_5+y_4=-5&(p_5-p_4)\\ \end{aligned} \]
現在觀察一下這些式子,每個變量都出現了兩次,一次為正一次為負,剛好可以抵消,而且所有等式的右邊和為0,我們再移下項,變成這樣(為了更加方便后面的建圖說明):
\[ \begin{aligned} &-x_1-x_2+y_1+2=0\\ &-x_3-x_2-y_1+y_2+1=0\\ &-x_4-x_5+x_1-y_2+y_3+1=0\\ &x_3+x_4-y_3+y_4+1=0\\ &x_5-y_4-5=0\\ \end{aligned} \]
那么接下來先說一下如何建圖,后面再解釋為什么:

  • 將第\(i\)個等式看做圖中編號為\(i\)的點,然后再添加兩個點\(vs\)\(vt\)作為源點和匯點

  • 對於一個等式\(i\)中的常數項\(c\),如果是非負整數,那么從源點\(vs\)連一條容量為\(c\)費用為\(0\)的有向邊到\(i\)

如果是負整數,那么從\(i\)連一條容量為\(abs(c)\)費用為\(0\)的有向邊到匯點\(vt\)

  • 對於一個等式\(i\)中的變量\(x_i\),如果\(x_i\)在等式\(j\)中出現,且在\(i\)\(j\)中符號相反,那么從符號為負的等式連一條容量為\(\infty\)費用為\(v_i\)的有向邊到符號為正的等式

(注意,這里的容量應該為\(x_i\)的上限,這題里面是只要求\(x_i>=0\)即可所以是\(\infty\))

  • 對於一個等式\(i\)中的變量\(y_i\),如果\(y_i\)在等式\(j\)中出現,且在\(i\)\(j\)中符號相反,那么從符號為負的等式連一條容量為\(\infty\)費用為\(0\)的有向邊到符號為正的等式

(同樣的,這里的容量應該為\(y_i\)的上限,這題里面也是恰好\(y_i>=0\)所以是\(\infty\)

建完圖之后,跑一遍最小費用最大流,\(ans\)就是\(vs\)\(vt\)的最小費用


怎么去理解呢?

我們看回上面的等式,每個等式的左邊都是幾個變量和一個常數相加,右邊都是0,會發現其實就像網絡流中的除了源點和匯點以外其他的點都滿足流量平衡(流進多少流出多少)

那么,我們就可以把一個式子中所有正的變量理解為流入的流量,負的變量理解為流出的流量,而正常數可以理解為來自源點的流量(一開始就流過來的一定有的),負的常數則是流向匯點的流量,所以就可以按照這個思路來構建這樣的一個網絡流模型啦,從源點到匯點的網絡最大流就可以滿足所有的等式

同時,這題還要求\(\sum\limits_{i=1}^{m}x_i*v_i\)最小,其實就是一個附加條件,那就給\(x\)變量對應的邊上加上權值然后跑最小費用最大流即可ovo


代碼的話大概是這樣的,順便吐槽一句bzoj這題的數據好像有點弱啊。。第一次交上去的時候反向邊兩個都建成tot+1了結果還要過了。。qwq

(其實就是說下面的代碼可能還是有bug歡迎糾錯qwq)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define ll long long
#define inf 1000000
using namespace std;
const int N=1010;
const int M=10010;
struct xxx{
    int y,next,r,op,c;
}a[M*10];
queue<int> q;
int h[N],X[N];
int pre[100010],mn[100010],pred[100010];
ll d[N];
ll ans;
bool vis[N];
int n,m,vs,vt,tot;
int add(int x,int y,int r,int c);
int spfa();

int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
#endif
    int l,r,c,tmp;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i) scanf("%d",X+i);
    vs=0,vt=n+2;
    memset(h,-1,sizeof(h));
    for (int i=2;i<=n+1;++i) add(i,i-1,inf,0);
    for (int i=1;i<=m;++i){
        scanf("%d%d%d",&l,&r,&c);
        add(l,r+1,inf,c);
    }
    for (int i=1;i<=n+1;++i){
        tmp=X[i]-X[i-1];
        if (tmp>0) add(vs,i,tmp,0);
        if (tmp<0) add(i,vt,-tmp,0);
    }
    ans=0;
    while (spfa());
    printf("%lld\n",ans);
}

int add(int x,int y,int r,int c){
    a[++tot].y=y; a[tot].next=h[x]; h[x]=tot; a[tot].r=r; a[tot].op=tot+1; a[tot].c=c;
    a[++tot].y=x; a[tot].next=h[y]; h[y]=tot; a[tot].r=0; a[tot].op=tot-1; a[tot].c=-c;
}

int spfa(){
    while (!q.empty()) q.pop();
    for (int i=vs;i<=vt;++i) d[i]=inf,vis[i]=false,mn[i]=inf;
    q.push(vs); vis[vs]=1; d[vs]=0;
    mn[vs]=inf; pre[vs]=-1; pred[vs]=-1;
    int v,u;
    while (!q.empty()){
        v=q.front(); q.pop();
        for (int i=h[v];i!=-1;i=a[i].next){
            u=a[i].y;
            if (!a[i].r) continue;
            if (d[u]>d[v]+a[i].c){
                pre[u]=i; pred[u]=v;
                mn[u]=min(mn[v],a[i].r);
                d[u]=d[v]+a[i].c;
                if (!vis[u])
                    vis[u]=1,q.push(u);
            }
        }
        vis[v]=false;
    }
    if (d[vt]==inf) return false;
    ll flow=mn[vt];
    ans+=flow*d[vt];
    u=vt;
    while (pre[u]!=-1){
        a[pre[u]].r-=flow;
        a[a[pre[u]].op].r+=flow;
        u=pred[u];
    }
    return true;
}

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com