网络流

来源:互联网 发布:华南理工大学 知乎 编辑:程序博客网 时间:2024/06/02 05:13

网络流

最大流

描述

在图中,给出每条边的最大流,求出从一给定源点到汇点的最大流

算法

单纯的贪心法由于搜索顺序会导致结果不同的问题并不能保证求出正确的答案,只需要将原始贪心算法中加入一个“反悔”机制就可以保证求出正确答案

实现中只需要加入反向边,没流过一条边,就将这条边的边权减去流量,并把反向边的边权加上这个流量

实际上就是通过加入反向边实现抵消操作

Ford-Fulkerson

dfs

时间复杂度:O(FE)

Edmonds-Karp

bfs优化

时间复杂度:O(E2V)

Dinic

每次找一条路前,先通过一次bfs更新每个点的level,目的是在之后的dfs中优先遍历深度浅的点

加入当前弧优化,避免对一条没有用的便进行多次检查

时间复杂度:O(EV2)

#include<iostream>#include<cstdio>#include<cstring>#include<queue>using namespace std;const int MAXN=10005,MAXM=100005,INF=~0U>>1;int N,M,S,T;struct E{int next,to,cap;} e[MAXM<<1];int ecnt=1,G[MAXN];void addEdge(int u,int v,int c){    e[++ecnt]=(E){G[u],v,c};G[u]=ecnt;    e[++ecnt]=(E){G[v],u,0};G[v]=ecnt;}int dfn[MAXN];queue<int> que;bool calDfn(){    int i;    memset(dfn,-1,sizeof(dfn));    dfn[S]=0;que.push(S);    while(!que.empty())    {        int u=que.front();que.pop();        for(i=G[u];i;i=e[i].next)            if(e[i].cap>0&&dfn[e[i].to]<0)            {                int v=e[i].to;                dfn[v]=dfn[u]+1;                que.push(v);            }    }    return dfn[T]>-1;}int iter[MAXN];int calF(int u,int f){    if(u==T) return f;    for(int & i=iter[u];i;i=e[i].next)    {        int v=e[i].to;        if(e[i].cap>0&&dfn[v]>dfn[u])        {            int res=calF(v,min(f,e[i].cap));            if(res>0)            {                e[i].cap-=res,e[i^1].cap+=res;                return res;            }        }    }    return 0;}int maxFlow(){    int i,flow=0;    while(calDfn())    {        for(i=1;i<=N;i++) iter[i]=G[i];        int f;        while((f=calF(S,INF))>0)            flow+=f;    }    return flow;}int main(){    int i;    scanf("%d%d%d%d",&N,&M,&S,&T);    for(i=1;i<=M;i++)    {        int u,v,w;scanf("%d%d%d",&u,&v,&w);        addEdge(u,v,w);    }    printf("%d",maxFlow());}

[网络流24题] 搭配飞行员 [COGS14]

题目描述

飞行大队有若干个来自各地的驾驶员,专门驾驶一种型号的飞机,这种飞机每架有两个驾驶员,需一个正驾驶员和一个副驾驶员。由于种种原因,例如相互配合的问题,有些驾驶员不能在同一架飞机上飞行,问如何搭配驾驶员才能使出航的飞机最多。

1

如图,假设有10个驾驶员,如图中的V1,V2,…,V10就代表达10个驾驶员,其中V1,V2,V3,V4,V5是正驾驶员,V6,V7,V8,V9,V10是副驾驶员。如果一个正驾驶员和一个副驾驶员可以同机飞行,就在代表他们两个之间连一条线,两个人不能同机飞行,就不连。例如V1和V7可以同机飞行,而V1和V8就不行。请搭配飞行员,使出航的飞机最多。注意:因为驾驶工作分工严格,两个正驾驶员或两个副驾驶员都不能同机飞行.

输入格式
输入文件有若干行
第一行,两个整数n与n1,表示共有n个飞行员(2<=n<=100),其中有n1名飞行员是正驾驶员.
下面有若干行,每行有2个数字a,b。表示正驾驶员a和副驾驶员b可以同机飞行。
注:正驾驶员的编号在前,即正驾驶员的编号小于副驾驶员的编号.

输出格式
输出文件有一行
第一行,1个整数,表示最大起飞的飞机数。

样例

flyer.in

10 5
1 7
2 6
2 10
3 7
4 8
5 9

flyer.out

4


裸题, 加源汇,全赋一,最大流。


#include<cstdio>#include<queue>#include<cstring>using namespace std;const int MAXV=205,MAXE=MAXV*MAXV,INF=~0U>>1;int n,n1,S,T;struct E{int next,to,cap;} e[MAXE<<1];int ecnt=1,G[MAXV];void addEdge(int u,int v,int c){    e[++ecnt]=(E){G[u],v,c};G[u]=ecnt;    e[++ecnt]=(E){G[v],u,0};G[v]=ecnt;}int dfn[MAXV];queue<int> que;bool calDfn(){    int i;    memset(dfn,-1,sizeof(dfn));    dfn[S]=0;que.push(S);    while(!que.empty())    {        int u=que.front();que.pop();        for(i=G[u];i;i=e[i].next)        {            int v=e[i].to;            if(e[i].cap>0&&dfn[v]==-1)                dfn[v]=dfn[u]+1,que.push(v);        }    }    return dfn[T]!=-1;}int iter[MAXV];int calF(int u,int f){    if(u==T) return f;    for(int & i=iter[u];i;i=e[i].next)    {        int v=e[i].to;        if(e[i].cap>0&&dfn[v]==dfn[u]+1)        {            int res=calF(v,min(f,e[i].cap));            if(res>0)            {                e[i].cap-=res,e[i^1].cap+=res;                return res;            }        }    }}int maxFlow(){    int i,res=0;    while(calDfn())    {        int f;for(i=1;i<=T;i++) iter[i]=G[i];        while((f=calF(S,INF))>0)            res+=f;    }    return res;}int main(){    freopen("flyer.in","r",stdin);    freopen("flyer.out","w",stdout);    int i,a,b;    scanf("%d%d",&n,&n1);    S=n+1,T=n+2;    for(i=1;i<=n1;i++) addEdge(S,i,1);    for(i=n1+1;i<=n;i++) addEdge(i,T,1);    while(scanf("%d%d",&a,&b)!=EOF) addEdge(a,b,1);    printf("%d\n",maxFlow());    return 0;}

[网络流24题] 太空飞行计划 [COGS727]

题目描述

W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已确定了一个可供选择的实验集合E={E1,E2,…,Em},和进行这些实验需要使用的全部仪器的集合I={ I1, I2,…,In }。实验Ej 需要用到的仪器是I的子集Rj∈I。配置仪器Ik 的费用为ck 美元。实验Ej 的赞助商已同意为该实验结果支付pj 美元。W教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。

对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。

输入格式

第1行有2个正整数m和n(m,n <= 100)。m是实验数,n是仪器数。接下来的m行,每行是一个实验的有关数据。第一个数赞助商同意支付该实验的费用;接着是该实验需要用到的若干仪器的编号。最后一行的n个数是配置每个仪器的费用。

输出格式

第1行是实验编号;第2行是仪器编号;最后一行是净收益。

样例

shuttle.in

2 3
10 1 2
25 2 3
5 6 7

shuttle.out

1 2
1 2 3
17


只要收入大于成本那么实验就可以做,故从源点每个实验连一条 cap 为收入的边,从每个实验仪器向汇点连一条 cap 为费用的边,这样,跑出的最大流就是总的花费了。

答案要求输出选择的实验和实验仪器,那么对于所有最后一次还能遍历到的点,说明其可以选择,则输出。最后的净利润即为总收益减去总花费。


#include<iostream>#include<cstdio>#include<queue>#include<cstring>using namespace std;const int MAXV=205,MAXE=4e4+205,INF=~0U>>1;int m,n,S,T;struct E{int next,to,cap;} e[MAXE<<1];int ecnt=1,G[MAXV];void addEdge(int u,int v,int c){    e[++ecnt]=(E){G[u],v,c};G[u]=ecnt;    e[++ecnt]=(E){G[v],u,0};G[v]=ecnt;}int dfn[MAXV];queue<int> que;bool calDfn(){    int i;    memset(dfn,-1,sizeof(dfn));    dfn[S]=0;que.push(S);    while(!que.empty())    {        int u=que.front();que.pop();        for(i=G[u];i;i=e[i].next)        {            int v=e[i].to;            if(e[i].cap>0&&dfn[v]==-1)                dfn[v]=dfn[u]+1,que.push(v);        }    }    return dfn[T]!=-1;}int iter[MAXV];int calF(int u,int f){    int i;    if(u==T) return f;    for(int & i=iter[u];i;i=e[i].next)    {        int v=e[i].to;        if(e[i].cap>0&&dfn[v]==dfn[u]+1)        {            int res=calF(v,min(f,e[i].cap));            if(res>0)            {                e[i].cap-=res,e[i^1].cap+=res;                return res;            }        }    }    return 0;}int dinic(){    int i,f,res=0;    while(calDfn())    {        for(i=1;i<=T;i++) iter[i]=G[i];        while((f=calF(S,INF))>0) res+=f;    }    return res;}int main(){    freopen("shuttle.in","r",stdin);    freopen("shuttle.out","w",stdout);    int i,j,x,sum=0;    scanf("%d%d",&m,&n);    S=m+n+1,T=S+1;    for(i=1;i<=m;i++)    {        scanf("%d",&x);addEdge(S,i,x);sum+=x;        while(getchar()==' ')            scanf("%d",&x),addEdge(i,x+m,INF);    }    for(i=1;i<=n;i++) scanf("%d",&x),addEdge(i+m,T,x);    int ans=sum-dinic();    for(i=1;i<=m;i++) if(dfn[i]!=-1) printf("%d ",i);putchar('\n');    for(i=1;i<=n;i++) if(dfn[i+m]!=-1) printf("%d ",i);putchar('\n');    printf("%d\n",ans);    return 0;}

最小割

最大流=最小割

最大流的变体

多个源点和汇点

增加一个超级源点s和一个超级汇点t,从s向每个源点加入一条容量无穷的边,从每一个汇点向t加入一条容量无穷的边

无向图

加两条边即可

顶点上流量限制

把结点“割开”,所有入边连到一点A上,所有出边连到一点B上,从A向B连一条cap为该结点流量限制的边即可

费用流

每条边上通过单位流量需要一定费用

#include<iostream>#include<cstdio>#include<cstring>#include<queue>using namespace std;const int MAXN=5005,MAXM=50005,INF=~0U>>1;int N,M,S,T;int minCost,maxFlow;struct E{int next,to,cap,cost;} e[MAXM<<1];int ecnt=1,G[MAXN];void addEdge(int u,int v,int c,int w){    e[++ecnt]=(E){G[u],v,c,w};G[u]=ecnt;    e[++ecnt]=(E){G[v],u,0,-w};G[v]=ecnt;}int pre[MAXN];//pre:最短路中来的边int dis[MAXN];bool inQ[MAXN];queue<int> que;bool SPFA(){    int i;    for(i=1;i<=N;i++) dis[i]=INF;    memset(inQ,false,sizeof(inQ));    inQ[S]=true,dis[S]=0;que.push(S);    while(!que.empty())    {        int u=que.front();que.pop();        for(i=G[u];i;i=e[i].next)        {            int v=e[i].to;            if(e[i].cap>0&&dis[v]>dis[u]+e[i].cost)            {                dis[v]=dis[u]+e[i].cost;                pre[v]=i;                if(!inQ[v])                {                    inQ[v]=true;                    que.push(v);                }            }        }        inQ[u]=false;    }    return dis[T]!=INF;}void calc(){    int f=INF,u;    for(u=T;u!=S;u=e[pre[u]^1].to) f=min(f,e[pre[u]].cap);    for(u=T;u!=S;u=e[pre[u]^1].to) e[pre[u]].cap-=f,e[pre[u]^1].cap+=f;    minCost+=f*dis[T];    maxFlow+=f;}int main(){    int i;    scanf("%d%d%d%d",&N,&M,&S,&T);    for(i=1;i<=M;i++)    {        int u,v,c,w;scanf("%d%d%d%d",&u,&v,&c,&w);        addEdge(u,v,c,w);    }    while(SPFA()) calc();    printf("%d %d\n",maxFlow,minCost);    return 0;}

[网络流24题] 运输问题 [COGS739]

时间限制:1 s 内存限制:128 MB

题目描述

题目描述

对于给定的 m 个仓库和 n 个零售商店间运送货物的费用,计算最优运输方案和最差运输方案。

输入格式

输入格式

输出格式

程序运行结束时,将计算出的最少运输费用和最多运输费用输出到文件 tran.out 中。

tran.in

2 3
220 280
170 120 210
77 39 105
150 186 122

tran.out

48500
69140

对于所有数据: 1N,M100


裸题,只需从原点到仓库连边,商店到汇点连边,前两种边带 cap ,商店向汇点连带费用的边,然后跑费用流即可。

至于最大费用,把费用设为负的跑费用流即可,注意不要用 Dijkstra 了。


#include<iostream>#include<cstdio>#include<cstring>#include<queue>using namespace std;const int MAXV=205,MAXE=MAXV*(MAXV+2),INF=~0U>>1;int n,m,S,T;int minCost;struct E{int next,to,cap,cost;};struct CFS{    E e[MAXE<<1];    int ecnt,G[MAXV];    void addEdge(int u,int v,int c,int w)    {        e[++ecnt]=(E){G[u],v,c,w};G[u]=ecnt;        e[++ecnt]=(E){G[v],u,0,-w};G[v]=ecnt;    }    CFS(){ecnt=1;}} G[2];int pre[MAXV];bool inQ[MAXV];int dis[MAXV];queue<int> que;bool SPFA(int flag){    CFS & rG=G[flag>0];    int i;    memset(inQ,false,sizeof(inQ));    for(i=1;i<=m+n+1;i++) dis[i]=INF;    dis[S]=0;que.push(S);inQ[S]=true;    while(!que.empty())    {        int u=que.front();que.pop();        inQ[u]=false;        for(i=rG.G[u];i;i=rG.e[i].next)        {            int v=rG.e[i].to;            if(rG.e[i].cap>0&&dis[v]>dis[u]+rG.e[i].cost*flag)            {                dis[v]=dis[u]+rG.e[i].cost*flag;                pre[v]=i;                if(!inQ[v]) inQ[v]=true,que.push(v);            }        }    }    return dis[T]!=INF;}void calc(int flag){    CFS & rG=G[flag>0];    int f=INF,u;    for(u=T;u!=S;u=rG.e[pre[u]^1].to)         f=min(f,rG.e[pre[u]].cap);    for(u=T;u!=S;u=rG.e[pre[u]^1].to)         rG.e[pre[u]].cap-=f,rG.e[pre[u]^1].cap+=f;    minCost+=f*dis[T];}int main(){    freopen("tran.in","r",stdin);    freopen("tran.out","w",stdout);    int i,j,c,w;    scanf("%d%d",&m,&n);    S=0,T=n+m+1;    //    for(i=1;i<=m;i++)        scanf("%d",&c),G[1].addEdge(S,i,c,0);    for(i=1;i<=n;i++)        scanf("%d",&c),G[1].addEdge(i+m,T,c,0);    for(i=1;i<=m;i++)        for(j=1;j<=n;j++)            scanf("%d",&w),G[1].addEdge(i,j+m,INF,w);    G[0]=G[1];    //    minCost=0;    while(SPFA(1))         calc(1);    printf("%d\n",minCost);    minCost=0;    while(SPFA(-1))        calc(-1);    printf("%d\n",-minCost);    return 0;}

有上下界的网络流

无源汇可行流

流程:

  1. 添加附加源点 S ,附加汇点 T ,将所有上界与下界之差作为 cap 值;
  2. 统计每个点的 nodeFlow ,入度为正,出度为负;
  3. 对于每个节点 u :
    • 若 nodeFlow 为正,从 S 到 u 建立一条 cap 为 nodeFlow 的边
    • 若 nodeFlow 为负,从 u 到 T 建立一条 cap 为 -nodeFlow 的边
  4. 从 S 到 T 跑最大流,对于每条边,其可行流为流量下界加上最大流中流过的流量。

有源汇可行流

添加一条从 t 到 s 的 cap 为 INF 的边,按无源汇跑即可。

有源汇最大流

0 0