網絡流


網絡流=帶反悔的貪心。——517

個人認為網絡流=最大流dinic/費用流板子+玄學意會建圖。

網絡流朴素算法ek

對於每條邊 \((u,v,w)\) ,建一條相應的反向邊 \((v,u,0)\)
算法執行時,先從源點s bfs,看看到t最多能流多少,對於每個節點記錄它的前驅節點,如果到t的流量不為0,那么從t回溯回s,將每條邊的容量減去流量,其反向邊的容量加上流量,然后把答案加上所有回溯到的邊的流量;否則停止執行,返回結果。最壞復雜度 \(O(nm^2)\)

#include<bits/stdc++.h>
#define maxn 10005
#define maxm 100005
#define INF 1050000000
using namespace std;
template<typename tp>
void read(tp& x){
    x=0;
    char c=getchar();
    bool sgn=0;
    while((c<'0'||c>'9')&&c!='-')c=getchar();
    if(c=='-')sgn=1,c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
    if(sgn)x=-x;
}
template<typename tp>
void write(tp x){
    if(x<0)putchar('-'),write(-x);
    else{
        if(x>=10)write(x/10);
        putchar(x%10+'0');
    }
}
struct edge{
    int to,next,w;
}e[maxm<<1];
int head[maxn],cnte;
void add(int u,int v,int w){
    e[++cnte].to=v;
    e[cnte].w=w;
    e[cnte].next=head[u];
    head[u]=cnte;
}
int n,m,s,t,pre[maxn],edg[maxn],flow[maxn];
bool bfs(){
    for(int i=1;i<=n;i++)pre[i]=edg[i]=-1,flow[i]=INF;
    pre[s]=0;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        if(u==t)break;
        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(e[i].w>0&&pre[v]==-1){
                pre[v]=u;
                edg[v]=i;
                flow[v]=min(flow[u],e[i].w);
                q.push(v);
            }
        }
    }
    return pre[t]!=-1;
}
int ek(){
    int ans=0;
    while(bfs()){
        int x=t;
        while(x!=s){
            e[edg[x]].w-=flow[t];
            e[edg[x]^1].w+=flow[t];
            x=pre[x];
        }
        ans+=flow[t];
    }
    return ans;
}
signed main(){
    read(n),read(m),read(s),read(t);
    for(int i=1;i<=n;i++)head[i]=-1;
    cnte=-1;
    for(int i=1;i<=m;i++){
        int u,v,w;
        read(u);read(v);read(w);
        add(u,v,w);
        add(v,u,0);
    }
    write(ek());
    return 0;
}

網絡流進階算法dinic

解釋不來,請上網搜索……
最壞復雜度 \(O(n^2m)\)
head1[]:當前弧優化,極小部分題會卡,如LOJ117,加了快10倍

#include<bits/stdc++.h>
using namespace std;
const int maxn=10003,maxm=200003,INF=1050000000;
struct edge{int to,next,w;}e[maxm<<1];
int head[maxn],head1[maxn],cnte;
void add(int u,int v,int w){e[++cnte].to=v,e[cnte].w=w,e[cnte].next=head[u],head[u]=cnte;}
void addedge(int u,int v,int w){add(u,v,w),add(v,u,0);}
int n,s,t,dep[maxn],q[maxn];
bool bfs(){
    for(int i=1;i<=n;i++)dep[i]=0,head1[i]=head[i];
    dep[s]=1;
    int *qhead=q,*qtail=q;
    *qtail++=s;
    while(qhead!=qtail){
        int u=*qhead++;
        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(e[i].w&&dep[v]==0){
                dep[v]=dep[u]+1;
                *qtail++=v;
            }
        }
    }
    return dep[t]!=0;
}
int dfs(int u,int low){
    if(low==0||u==t)return low;
    int flow=0;
    for(int &i=head1[u];~i;i=e[i].next){
        int v=e[i].to;
        if(e[i].w&&dep[v]==dep[u]+1){
            int tmp=dfs(v,min(low,e[i].w));
            if(tmp==0)dep[v]=0;
            else{
                flow+=tmp;
                low-=tmp;
                e[i].w-=tmp;
                e[i^1].w+=tmp;
                if(low==0)break;
            }
        }
    }
    return flow;
}
int dinic(){
    int ans=0;
    while(bfs())ans+=dfs(s,INF);
    return ans;
}
int main(){
    int m;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=n;i++)head[i]=-1;
    cnte=-1;
    while(m--){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
    }
    printf("%d",dinic());
    return 0;
}

費用流朴素算法spfa+ek

對於每條邊 \((u,v,w,cost)\) ,建反向邊 \((v,u,w,-cost)\)
該算法即在最大流ek的基礎上把bfs改為spfa。為什么不能直接用dijkstra?因為有負環。

#include<bits/stdc++.h>
#define maxn 5005
#define maxm 100005
#define INF 1050000000
using namespace std;
struct edge{int to,next,w,cost;}e[maxm<<1];
int head[maxn],cnte;
void add(int u,int v,int w,int cost){e[++cnte].to=v,e[cnte].w=w,e[cnte].cost=cost,e[cnte].next=head[u],head[u]=cnte;}
void addedge(int u,int v,int w,int cost){add(u,v,w,cost),add(v,u,w,-cost);}
int n,s,t,pre[maxn],edg[maxn],flow[maxn],maxflow,dis[maxn],mincost;
bool vis[maxn];
bool spfa(){
    for(int i=1;i<=n;i++)flow[i]=dis[i]=INF,vis[i]=0;
    pre[t]=-1;
    dis[s]=0;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(e[i].w>0&&dis[u]+e[i].cost<dis[v]){
                dis[v]=dis[u]+e[i].cost;
                pre[v]=u;
                edg[v]=i;
                flow[v]=min(flow[u],e[i].w);
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return pre[t]!=-1;
}
void micmxf(){
    while(spfa()){
        int x=t;
        while(x!=s){
            e[edg[x]].w-=flow[t];
            e[edg[x]^1].w+=flow[t];
            x=pre[x];
        }
        maxflow+=flow[t];
        mincost+=flow[t]*dis[t];
    }
}
signed main(){
    int m;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=0;i<=n;i++)head[i]=-1;
    cnte=-1;
    for(int i=1;i<=m;i++){
        int u,v,w,cost;
        scanf("%d%d%d%d",&u,&v,&w,&cost);
        addedge(u,v,w,cost);
    }
    micmxf();
    printf("%d %d\n",maxflow,mincost);
    return 0;
}

網絡流常用建圖方法

BZOJ1001

最朴素的建模。
直接在原圖上源點 \(s=1\) ,匯點 \(t=n*m\) 跑最大流即可。

網絡流24題 飛行員配對方案問題

理解最大流和二分圖匹配的關系。
本題構造一個二分圖,對於所有左側點 \(i\) ,連邊 \((s,i,INF)\) ;對於所有右側點 \(j\) ,連邊 \((j,t,INF)\) ,對於二分圖的每條邊,連邊 \((u,v,1)\)

網絡流24題 最小路徑覆蓋問題

最小路徑覆蓋。
先將每個點 \(u\) 拆成一個入點 \(u_1\) 和一個出點 \(u_2\)
\((s,i_1,1),(i_2,t,1),(u_2,v_1,1),(i_1,i_2,1)\)
求最大流。

NOI2006 最大獲利

對於每個物品有一個收益,選一些物品要付出一個代價,求最大收益的題目往往用最小割解決。
本題中,設 \(i\) 為中轉站, \((a_j,b_j,c_j)\) 為用戶需求,連邊 \((s,i,p_i),(j,t,c_j),(a_j,b_j,INF)\) ,然后求最大流(最小割),答案為所有收益的和-最小割。可以發現最小割=最小成本+舍棄的收益。

網絡流24題 方格取數問題

也用最小割解決。
先對圖黑白染色,然后設 \(i\) 為黑點, \(j\) 為白點, \(k\) 為與 \(i\) 相鄰的點,連邊 \((s,i,a_i),(j,t,a_j),(i,k,INF)\)

CQOI2009 跳舞

先考慮如何驗證給定的舞曲數目是否有解。
構造最大流,把每個人拆成兩個點,男孩 \(i_1,i_2\) ,女孩 \(i_3,i_4\) ,連邊 \((s,i_1,INF),(i_1,i_4,1),(i_1,i_2,k),(i_2,i_3,1),(i_3,i_4,k),(i_4,t,INF)\) ,如果最大流=舞曲數目*人數,那么可行,否則不可行。
然后我們只需要二分舞曲數目。

TJOI2015 線性代數

經過推算, \(D=\sum_{i=1}^n \sum_{j=1}^n a_i*a_j*b_{i,j}-\sum_{i=1}^n a_i*c_i\)
這和上面講的最小割類似,故用最小割解決。
連邊 \((s,i,c_i),(i,(i,j),INF),(j,(i,j),INF),((i,j),t,b_{i,j})\)

CQOI2012 交換棋子

費用流。
首先把每一個點拆成三個點 \(i_1,i_2,i_3\)
\(j\) 是與 \(i\) 八連通的點,若 \(i\) 初始狀態為1,連邊 \((s,i_2,1,0)\) ;若 \(i\) 末狀態為1,連邊 \((i_2,t,1,0)\)
對於所有節點,連邊 \((i_1,i_2,w_1,0),(i_2,i_3,w_2,1),(i_3,j_1,INF,0)\) ,其中若 \(m_{i,j}\) 為奇數且 \((i,j)\) 初狀態為1,末狀態為0, \(w_1=\lfloor \frac{m}{2} \rfloor,w_2=\lfloor \frac{m+1}{2} \rfloor\) ;若 \(m_{i,j}\) 為奇數且 \((i,j)\) 初狀態為0,末狀態為1, \(w_1=\lfloor \frac{m+1}{2} \rfloor,w_2=\lfloor \frac{m}{2} \rfloor\) ;否則 \(w_1=\frac{m}{2},w_2=\frac{m}{2}\)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM