网络流:最大流以及费用流的总结

来源:互联网 发布:网络考试系统破解版 编辑:程序博客网 时间:2024/05/21 17:23

前言:

(终于学会了网络流好开心)网络流是一类问题的统称,实际上很多问题都可以转成网络流来整,所以在此总结一下。

最大流

方法常见的是FordFulkerson方法,这个之所以叫方法,是因为这个有很多不同版本的实现,最常见的实现是dinic。这个方法的核心是不停的寻找増广路,直到没有增广路为止。

dinic

算法的实现是不直接像FF方法所说的以来就找增广路,而是先用bfs将整个图分层,然后再在一层图中的点不算做增广路的路径,这样子的话,避免了找很多额外的边,这样子的话,可以证明复杂度是O(EV2)的,但是实际上根本就跑不到这个复杂度,而且还有一些优化,导致在随机图中可以过1e5的数据都没有问题。
而且我们在做的时候,还可以有一个叫做当前弧优化的大法,方法是将肯定不可能的边跳过,这样就大大增快了速度。
下面提供两个版本的dinic,分别是前缀星以及邻接表的储存方式。(是根据《挑战算法程序竞赛》的版改来的)
前缀星:

#include <bits/stdc++.h>using namespace std;const int N = 10005;const int M = 100005;const int inf = 1000000000;struct E{int to,cap,nex;}e[M*2];int head[N],ecnt;int dep[N];int iter[N];int n,m,s,t;void adde(int fr,int to,int cap){    e[ecnt] = (E){to,cap,head[fr]};    head[fr] = ecnt++;    e[ecnt] = (E){fr,0,head[to]};    head[to] = ecnt++;}void bfs(int s){    memset(dep,-1,sizeof dep);    queue <int> q;    dep[s] = 0;    q.push(s);    while (!q.empty())    {        int cur = q.front();q.pop();        for (int j=head[cur];j!=-1;j=e[j].nex)        {            if (e[j].cap >0 && dep[e[j].to] < 0)            {                dep[e[j].to] = dep[cur] + 1;                q.push(e[j].to);            }        }    }}int dfs(int o,int t,int flow){    if (o==t) return flow;    for (int &j=iter[o];j!=-1;j=e[j].nex)    {        if (e[j].cap > 0 && dep[e[j].to] == dep[o] + 1)        {            int tmpflow = dfs(e[j].to,t,min(flow,e[j].cap));            if (tmpflow > 0)            {                e[j].cap -= tmpflow;                e[j^1].cap += tmpflow;                return tmpflow;            }        }    }    return 0;}int dinic(int s,int t){    int ansflow = 0;    for (;;)    {        bfs(s);        if (dep[t] < 0) return ansflow;        for (int i=1;i<=n;i++) iter[i]=head[i];        int f;        while ( (f=dfs(s,t,inf)) > 0)        {            ansflow += f;        }    }}void init(){    ecnt=0;    memset(head,-1,sizeof head);}

邻接表:

#include <bits/stdc++.h>#define pb push_backusing namespace std;const int N = 10005;const int inf = 1000000000;struct E{int to,cap,rev;};vector <E> G[N];int dep[N];int iter[N];int n,m,s,t,e;void adde(int fr,int to,int cap){    G[fr].pb((E){to,cap,G[to].size()});    G[to].pb((E){fr,0,G[fr].size()-1});}void bfs(int s){    memset(dep,-1,sizeof dep);    queue <int> q;    dep[s] = 0;    q.push(s);    while (!q.empty())    {        int cur = q.front();q.pop();        for (int i=0;i<G[cur].size();i++)        {            E &e = G[cur][i];            if (e.cap >0 && dep[e.to] < 0)            {                dep[e.to] = dep[cur] + 1;                q.push(e.to);            }        }    }}int dfs(int o,int t,int flow){    if (o==t) return flow;    for (int &i=iter[o];i<G[o].size();i++)    {        E &e = G[o][i];        if (e.cap > 0 && dep[e.to] == dep[o] + 1)        {            int tmpflow = dfs(e.to,t,min(flow,e.cap));            if (tmpflow > 0)            {                e.cap -= tmpflow;                G[e.to][e.rev].cap += tmpflow;                return tmpflow;            }        }    }    return 0;}int dinic(int s,int t){    int ansflow = 0;    for (;;)    {        bfs(s);        if (dep[t] < 0) return ansflow;        memset(iter,0,sizeof iter);        int f;        while ( (f=dfs(s,t,inf)) > 0)        {            ansflow += f;        }    }}

费用流

常见的费用流有两种,一种是求流量为F的费用流,另外一种是求最大流最小费用流。如何做呢?我们考虑一下我们是怎么做最大流的,我们是将增广路按照距离来bfs分层,那么这个我们也可以模仿此,但是每次我们怎么走呢?我们按照费用的最小来走,这样子的话,就很明显了,但是要注意,不要乱写dijkstral,要写BellmanFord,因为路上的边权可能是负的。当然也可以写dijkstral,但是要用一下势函数h,借助类似差分的思想将边权变为正。下面是最小费用最大流的代码(SPFA):

#include<bits/stdc++.h>using namespace std;const int N = 5002;const int M = 500005;const int inf = 100000;struct E{    int to,cap,cost,flow,next;}e[2*M];int head[N] , ecnt;int  pre[N];int dis[N];bool vis[N];int n,m,S,T;void Clear(){    ecnt = 0;    memset(head,-1,sizeof head);}void adde(int fr,int to,int cap,int cost){    e[ecnt]=(E){to,cap,cost,0,head[fr]};    head[fr] = ecnt++;    e[ecnt]=(E){fr,0,-cost,0,head[to]};    head[to] = ecnt++;}bool SPFA(int s,int t){    memset(vis,0,sizeof vis);    memset(dis,127,sizeof dis);    memset(pre,-1,sizeof pre);    queue <int> q;    q.push(s);dis[s] = 0;vis[s]=1;    while (!q.empty())    {        int cur = q.front();q.pop();vis[cur] = false;        for (int j=head[cur];j!=-1;j=e[j].next)        {            int to = e[j].to;            if (dis[to] > dis[cur] + e[j].cost && e[j].cap > e[j].flow )            {                dis[to] = dis[cur] + e[j].cost;                pre[to] = j;                if (!vis[to])                {                    q.push(to);                    vis[to] = true;                }            }        }    }    return pre[t] != -1;}void MCMF (int s,int t,int &maxflow,int &mincost){    maxflow = mincost = 0;    while (SPFA(s,t))    {        int MIN = inf;        for (int j=pre[t]; j!=-1;j=pre[e[j^1].to])        {            MIN = min(MIN,e[j].cap - e[j].flow);        }        for (int j=pre[t]; j!=-1;j=pre[e[j^1].to])        {            e[j].flow += MIN;            e[j^1].flow -= MIN;            mincost += MIN * e[j].cost;        }        maxflow += MIN;    }}

还有一大坑点:建图的时候要从0号边开始,不然他的反边就不是ixor1