网络流最大流入门(洛谷P3376)

来源:互联网 发布:淘宝swatch是真的吗 编辑:程序博客网 时间:2024/06/04 19:42

网络流定义

所谓网络或容量网络指的是一个连通的赋权有向图 D= (V、E、C) , 其中V 是该图的顶点集,E是有向边(即弧)集,C是弧上的容量。此外顶点集中包括一个起点和一个终点。

形象点说,可以把每条边比作一个水管,每个水管都有一个流量上界(即最多能通过多少水)与当前流量(即当前流过多少水),而网络流指的就是类似的这样一张图。

最大流问题

定义

给你一个源点(可以把它看做水源)以及一个汇点(可以把它看做水池),求从源点到汇点的最大流量。

求解最大流

求解最大流有两种方法:

EK算法

先介绍几个定义:
残量网络:即边i剩余的流量。
后向弧:即边i的反向边,它的流量上界为0。
增广路:即从源点到汇点的一条路径,满足经过的边残量网络均>0.

EK算法就是不停地找增广路,每找到一条就修改路径上边的流量,直到找不到增广路为止。此时的总量即为答案。

看上去很暴力对不对?
但是因为一般情况找增广路不会太多次就没有增广路了,因此复杂度玄学。

①找增广路
找增广路时用到了BFS,每找到一条残量网络>0的边,就把它所指向的节点加入队列中。如果指向的是汇点就直接返回答案。如果做到队空则说明没有增广路。

int bfs(int now){//增广路,now表示源点    memset(f,false,sizeof(f));    int r=0,w=1;    que[1]=now; f[now]=true;    rem[s]=0x7fffffff;//刚开始把源点的流量改为∞    while (r<w){        int x=que[++r];        for (int i=h[x];~i;i=ed[i].next){            if (!f[ed[i].to]&&ed[i].v>ed[i].flow){//如果当前残量网络大于0                f[ed[i].to]=true;                que[++w]=ed[i].to;//加入队列                fa[ed[i].to].x=x;                fa[ed[i].to].e=i;                rem[ed[i].to]=min(rem[x],ed[i].v-ed[i].flow);//当前最小残量网络                if (ed[i].to==t) return rem[t];            }        }    }    return 0;}

②修改路径流量
修改路径流量时用到了后向弧。如果经过这条边,就把它的流量加上当前得到的答案,把它的对应边减去当前得到的答案。因为如果经过了后向弧则说明它往回走了,此时应减小流量。

void change(int remain){//remain表示当前增广出来的答案    int now=t;    while (now!=s){        int e=fa[now].e;        ed[e].flow+=remain;//增加当前边的流量        ed[e^1].flow-=remain;//减少对应边的流量        now=fa[now].x;    }}

算法模板:

以洛谷P3376为例:

#include<cstdio>#include<cstring>#include<algorithm>#define MAXN 100000#define MAXM 1000000using namespace std;struct edge{    int next,to;    int flow,v;};struct father{    int x,e;};int n,m,k,s,t;int h[MAXN+5];edge ed[2*MAXM+5];father fa[MAXN+5];int que[MAXN+5],rem[MAXN+5];bool f[MAXN+5];inline char readc(){    static char buf[100000],*l=buf,*r=buf;    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);    if (l==r) return EOF;    return *l++;}inline int _read(){    int num=0; char ch=readc();    while (ch<'0'||ch>'9') ch=readc();    while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); }    return num;}void addedge(int x,int y,int z){    ed[k].next=h[x]; ed[k].to=y; ed[k].v=z; h[x]=k++;    ed[k].next=h[y]; ed[k].to=x; ed[k].v=0; h[y]=k++;}int bfs(int now){    memset(f,false,sizeof(f));    int r=0,w=1;    que[1]=now; f[now]=true;    rem[s]=0x7fffffff;    while (r<w){        int x=que[++r];        for (int i=h[x];~i;i=ed[i].next){            if (!f[ed[i].to]&&ed[i].v>ed[i].flow){                f[ed[i].to]=true;                que[++w]=ed[i].to;                fa[ed[i].to].x=x;                fa[ed[i].to].e=i;                rem[ed[i].to]=min(rem[x],ed[i].v-ed[i].flow);                if (ed[i].to==t) return rem[t];            }        }    }    return 0;}void change(int remain){    int now=t;    while (now!=s){        int e=fa[now].e;        ed[e].flow+=remain;        ed[e^1].flow-=remain;        now=fa[now].x;    }}int maxflow(){    int ans=0;    while (1){        int sum=bfs(s);        if (!sum) return ans;        ans+=sum;        change(sum);    }}int main(){    memset(h,-1,sizeof(h));    n=_read(); m=_read(); s=_read(); t=_read();    for (int i=1;i<=m;i++){        int u=_read(),v=_read(),d=_read();        addedge(u,v,d);    }    printf("%d\n",maxflow());    return 0;}

Dinic算法

dinic就是在增广路上进行了改进。它运用到了分层图的思想,先BFS进行分层,再DFS增广。每次增广时仅当它指向的节点的层次=该节点的层次+1时进行增广。增广同时修改流量。

①BFS分层
同EK一样,只是多求了一个层次而已。

bool bfs(){    memset(f,false,sizeof(f));    int r=0,w=1; dis[s]=0; f[s]=true; que[1]=s;    while (r<w){        int x=que[++r];        for (int i=h[x];~i;i=ed[i].next)            if (!f[ed[i].to]&&ed[i].v>ed[i].flow){                dis[ed[i].to]=dis[x]+1;                f[ed[i].to]=true;                que[++w]=ed[i].to;            }    }    return f[t];}

②DFS增广
具体见注释:

int dfs(int x,int rem){//x为当前节点,rem为当前最小残量    if (x==t||rem==0) return rem;//如果已经到汇点了或者残量为0就直接返回    int sum=0;    for (int &i=cop[x];~i;i=ed[i].next)//直接从上次做过的地方做        if (dis[ed[i].to]==dis[x]+1){//分层图            int p=dfs(ed[i].to,min(ed[i].v-ed[i].flow,rem));            if (p){//如果找到了                sum+=p; ed[i].flow+=p; ed[i^1].flow-=p; rem-=p;//修改            }        }    return sum;}

算法模板

仍然是洛谷P3376

#include<cstdio>#include<cstring>#include<algorithm>#define MAXN 100000#define MAXM 1000000using namespace std;struct edge{    int next,to,v,flow;};int n,m,s,t,k;int h[MAXN+5],dis[MAXN+5],cop[MAXN+5],que[MAXN+5];edge ed[MAXM*2+5];bool f[MAXN+5];inline char readc(){    static char buf[100000],*l=buf,*r=buf;    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);    if (l==r) return EOF; return *l++;}inline int _read(){    int num=0; char ch=readc();    while (ch<'0'||ch>'9') ch=readc();    while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); }    return num;}void addedge(int x,int y,int z){    ed[k].next=h[x]; ed[k].to=y; ed[k].v=z; h[x]=k++;    ed[k].next=h[y]; ed[k].to=x; ed[k].v=0; h[y]=k++;}bool bfs(){    memset(f,false,sizeof(f));    int r=0,w=1; dis[s]=0; f[s]=true; que[1]=s;    while (r<w){        int x=que[++r];        for (int i=h[x];~i;i=ed[i].next)            if (!f[ed[i].to]&&ed[i].v>ed[i].flow){                dis[ed[i].to]=dis[x]+1;                f[ed[i].to]=true;                que[++w]=ed[i].to;            }    }    return f[t];}int dfs(int x,int rem){    if (x==t||rem==0) return rem;    int sum=0;    for (int &i=cop[x];~i;i=ed[i].next)        if (dis[ed[i].to]==dis[x]+1){            int p=dfs(ed[i].to,min(ed[i].v-ed[i].flow,rem));            if (p){                sum+=p; ed[i].flow+=p; ed[i^1].flow-=p; rem-=p;            }        }    return sum;}int maxflow(){    int ans=0;    while (bfs()){        memcpy(cop,h,sizeof(cop));        ans+=dfs(s,0x7fffffff);    }    return ans;}int main(){    memset(h,-1,sizeof(h));    n=_read(); m=_read(); s=_read(); t=_read();    for (int i=1;i<=m;i++){        int u=_read(),v=_read(),d=_read();        addedge(u,v,d);    }    printf("%d\n",maxflow());    return 0;}
原创粉丝点击