博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

网络流入门笔记(一)

网络流,主要用来解决流量问题。

常见的有最大流,与最小割。

网络流的之所以成为了省选算法,难就难在建模上。

因为这是第一篇,所以先介绍一下基础概念和模板写法。

详细的概念知识可以看大佬博客


我在这里放几个基础概念。

网络流:

所有弧上流量的集合f={f(u,v)},称为该容量网络的一个网络流.

定义:带权的有向图G=(V,E),满足以下条件,则称为网络流图(flow network):

  1. 仅有一个入度为0的顶点s,称s为源点
  2. 仅有一个出度为0的顶点t,称t为汇点
  3. 每条边的权值都为非负数,称为该边的容量,记作c(i,j)。
  4. 弧的流量:通过容量网络G中每条弧< u,v >,上的实际流量(简称流量),记为f(u,v)

性质

对于任意一个时刻,设f(u,v)实际流量,则整个图G的流网络满足3个性质:

  1. 容量限制:对任意u,v∈V,f(u,v)≤c(u,v)。
  2. 反对称性:对任意u,v∈V,f(u,v) = -f(v,u)。从u到v的流量一定是从v到u的流量的相反值。
  3. 流守恒性:对任意u,若u不为S或T,一定有∑f(u,v)=0,(u,v)∈E。即u到相邻节点的流量之和为0,因为流入u的流量和u点流出的流量相等,u点本身不会"制造"和"消耗"流量。

最大流

对于网络流图G,流量最大的可行流f,称为最大流

无向图的割集(Cut Set):C[A,B]是将图G分为A和B两个点集 A和B之间的边的全集 网络的割集:C[S,T]是将网络G分为s和t两部分点集 S属于s且T属于t 从S到T的边的全集 带权图的割(Cut):就是割集中边或者有向边的权和

通俗的理解一下: 割集好比是一个恐怖分子 把你家和自来水厂之间的水管网络砍断了一些 然后自来水厂无论怎么放水 水都只能从水管断口哗哗流走了 你家就停水了 割的大小应该是恐怖分子应该关心的事 毕竟细管子好割一些 而最小割花的力气最小

最大流最小割定理:

在容量网络中,满足弧流量限制条件,且满足平衡条件并且具有最大流量的可行流,称为网络最大流,简称最大流.


 


 

基础的一些概念解释完毕,那么正片开始。

我们来介绍两个算法:

1、EK:

这个算法应该是最好理解的算法。(对蒟蒻我而言)

首先我们要明确,我们现在要求的是最大流(即最小割)。

也就是说从s->t的路径中满足f(u,v)<=c(u,v);

一个很显而易见的思想就是,我们每一次通过bfs找一条增广路,然后进行增广。

然而在增广的过程中,可能会之前流来的水占用了一条管子,导致当前的水无法向前流。

这时候我们用加反向边来解决,每次增广后,让增广路上的正向边+minflow,反向边-minflow。

然后在下次增广时如果碰到了反向边>0,就说明可以试着让之前增广来的这条路径,回过头去找一下是否还有另外一条路径。

可能讲的不是很清楚,就有点儿像优化回溯的感觉。

然后一直增广,直到找不到增广路时,就得到了最大流。

注意一下边从偶数开始建,这样可以用i^1表示i的反向边。

代码

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int  Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=1e4+5,M=1e5+5,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1;
bool vis[N];
struct edge
{
    int nxt,to,c;
}e[M<<1];
struct rec
{
    int from,ed; 
}pre[N];
/**/
inline void add(int u,int v,int c)
{
    e[++cnt]=(edge){head[u],v,c};
    head[u]=cnt;
}
bool bfs()
{
    queue<int>q;
    memset(vis,0,sizeof(vis));
    memset(pre,0,sizeof(pre));
    vis[s]=1;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=head[u];i;i=e[i].nxt)
        {
            int v=e[i].to;
            if(!vis[v]&&e[i].c)
            {
                pre[v].from=u;
                pre[v].ed=i;
                if(v==t) return 1;
                vis[v]=1;
                q.push(v);
            }
        }
    }
    return 0;
}
int EK()
{
    int maxflow=0;
    while(bfs())
    {
        int mi=inf;
        for(int i=t;i!=s;i=pre[i].from)
        {
            mi=min(mi,e[pre[i].ed].c);
        }
        for(int i=t;i!=s;i=pre[i].from)
        {
            e[pre[i].ed].c-=mi;
            e[pre[i].ed^1].c+=mi;
        }
        maxflow+=mi;
    }
    return maxflow;
}
int main()
{
    read(n);read(m);read(s);read(t);
    for(int i=1,a,b,c;i<=m;i++)
    {
        read(a);read(b);read(c);
        add(a,b,c);
        add(b,a,0);
    }
    cout<<EK();
    return 0;
}
EK网络最大流

 

最小费用最大流的EK就是把bfs改成spfa按费用跑最短路:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int  Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=5005,M=50005,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1,dis[N],mi;
bool vis[N];
struct edge
{
    int nxt,to,c,w;
}e[M<<1];
struct PRE
{
    int from,edge; 
}pre[N];
/**/
inline void add(int u,int v,int c,int w)
{
    e[++cnt]=(edge){head[u],v,c,w};
    head[u]=cnt;
}
bool bfs()
{
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    memset(pre,0,sizeof(pre));
    vis[s]=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].nxt)
        {
            int v=e[i].to;
            if(dis[v]>dis[u]+e[i].w&&e[i].c)
            {
                dis[v]=dis[u]+e[i].w;
                pre[v].from=u;
                pre[v].edge=i;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return dis[t]!=inf;
}
void EK()
{
    int maxflow=0,mincost=0;
    while(bfs())
    {
        int mi=inf;
        for(int i=t;i!=s;i=pre[i].from)
        {
            mi=min(mi,e[pre[i].edge].c);
        }
        for(int i=t;i!=s;i=pre[i].from)
        {
            e[pre[i].edge].c-=mi;
            e[pre[i].edge^1].c+=mi;
        }
        maxflow+=mi;
        mincost+=mi*dis[t];
    }
    W(maxflow),space,W(mincost),endl;
}
int main()
{
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    read(n);read(m);read(s);read(t);
    for(int i=1,a,b,c,d;i<=m;i++)
    {
        read(a);read(b);read(c);read(d);
        add(a,b,c,d);
        add(b,a,0,-d);
    }
    EK();
    return 0;
}
EK最小费用最大流

 


 

2、Dinic

我们来回想一下上面的EK。

每一次都只找了一条增广路进行增广。

那么我们有没有办法可以一次找尽可能多的增广路进行增广呢?

于是就有了Dinic这个算法。

我们通过bfs把每个点的深度跑出来,再根据深度把整个图分层。

然后通过dfs进行增广。

具体讲解见洛谷日报:Dinic详解

然后Dinic可以通过弧优化跑得飞快,弧优化原理也很简单:

  首先,我们在按顺序dfs时,先被遍历到的边肯定是已经增广过了(或者已经确定无法继续增广了),那么这条边就可以视为“废边”

  那么下次我们再到达该节点时,就可以直接无视掉所有废边,只走还有用的边,也就是说,每次dfs结束后,下次dfs可以更省时间。

以下分别是Dinic的普通->优化->弧优版本。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int  Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=1e4+5,M=1e5+5,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1,dep[N];
bool vis[N];
struct edge
{
    int nxt,to,c;
}e[M<<1];
/**/
inline void add(int u,int v,int c)
{
    e[++cnt]=(edge){head[u],v,c};
    head[u]=cnt;
}
bool bfs()
{
    memset(dep,0x3f,sizeof(dep));
    memset(vis,0,sizeof(vis));
    dep[s]=0;
    vis[s]=1;
    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].nxt)
        {
            int v=e[i].to;
            if(dep[v]>dep[u]+1&&e[i].c)
            {
                dep[v]=dep[u]+1;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return dep[t]!=inf;
}
int dfs(int u,int flow)
{
    int mi=0;
    if(u==t) return flow;
    for(int i=head[u];i;i=e[i].nxt)
    {
        int v=e[i].to;
        if(dep[v]==dep[u]+1&&e[i].c)
        {
            if((mi=dfs(v,min(flow,e[i].c)))!=0)
            {
                e[i].c-=mi;
                e[i^1].c+=mi;
                return mi;
            }
        }
    }
    return 0;
}
void Dinic()
{
    int mi=0,maxflow=0;
    while(bfs())
    {
        while((mi=dfs(s,inf))!=0) maxflow+=mi;
    }
    W(maxflow);
}
int main()
{
    read(n);read(m);read(s);read(t);
    for(int i=1,a,b,c;i<=m;i++)
    {
        read(a);read(b);read(c);
        add(a,b,c);
        add(b,a,0);
    }
    Dinic();
}
Dinic网络最大流
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int  Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=1e4+5,M=1e5+5,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1,dep[N],maxflow;
bool vis[N],flag;
struct edge
{
    int nxt,to,c;
}e[M<<1];
/**/
inline void add(int u,int v,int c)
{
    e[++cnt]=(edge){head[u],v,c};
    head[u]=cnt;
}
bool bfs()
{
    memset(dep,0x3f,sizeof(dep));
    memset(vis,0,sizeof(vis));
    dep[s]=0;
    vis[s]=1;
    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].nxt)
        {
            int v=e[i].to;
            if(dep[v]>dep[u]+1&&e[i].c)
            {
                dep[v]=dep[u]+1;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return dep[t]!=inf;
}
int dfs(int u,int flow)
{
    if(u==t)
    {
        flag=1;
        maxflow+=flow;
        return flow;
    }
    int used=0,mi=0;
    for(int i=head[u];i;i=e[i].nxt)
    {
        int v=e[i].to;
        if(dep[v]==dep[u]+1&&e[i].c)
        {
            if((mi=dfs(v,min(flow-used,e[i].c)))!=0)
            {
                used+=mi;
                e[i].c-=mi;
                e[i^1].c+=mi;
                if(used==flow) break;
            }
        }
    }
    return used;
}
void Dinic()
{
    while(bfs())
    {
        flag=1;
        while(flag)
        {
            flag=0;
            dfs(s,inf);
        }
    }
    W(maxflow);
}
int main()
{
    read(n);read(m);read(s);read(t);
    for(int i=1,a,b,c;i<=m;i++)
    {
        read(a);read(b);read(c);
        add(a,b,c);
        add(b,a,0);
    }
    Dinic();
}
Dinic优化1
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int  Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=1e4+5,M=1e5+5,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1,dep[N],maxflow,cur[N];
bool vis[N],flag;
struct edge
{
    int nxt,to,c;
}e[M<<1];
/**/
inline void add(int u,int v,int c)
{
    e[++cnt]=(edge){head[u],v,c};
    head[u]=cnt;
}
bool bfs()
{
    // memset(dep,0x3f,sizeof(dep));
    // memset(vis,0,sizeof(vis));
    F(i,1,n) cur[i]=head[i],dep[i]=inf,vis[i]=0;
    dep[s]=0;
    vis[s]=1;
    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].nxt)
        {
            int v=e[i].to;
            if(dep[v]>dep[u]+1&&e[i].c)
            {
                dep[v]=dep[u]+1;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return dep[t]!=inf;
}
int dfs(int u,int flow)
{
    if(u==t)
    {
        flag=1;
        maxflow+=flow;
        return flow;
    }
    int used=0,mi=0;
    for(int i=cur[u];i;i=e[i].nxt)
    {
        cur[u]=i;
        int v=e[i].to;
        if(dep[v]==dep[u]+1&&e[i].c)
        {
            if((mi=dfs(v,min(flow-used,e[i].c)))!=0)
            {
                used+=mi;
                e[i].c-=mi;
                e[i^1].c+=mi;
                if(used==flow) break;
            }
        }
    }
    return used;
}
void Dinic()
{
    while(bfs())
    {
        flag=1;
        while(flag)
        {
            flag=0;
            dfs(s,inf);
        }
    }
    W(maxflow);
}
int main()
{
    read(n);read(m);read(s);read(t);
    for(int i=1,a,b,c;i<=m;i++)
    {
        read(a);read(b);read(c);
        add(a,b,c);
        add(b,a,0);
    }
    Dinic();
}
Dinic弧优化

 

至于最小费用最大流就基本上差不多了,在dfs过程中加上vis,防止多次遍历就好。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int  Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=5005,M=50005,inf=0x3f3f3f3f;
int n,m,s,t,dis[N],maxflow,mincost,cnt=1,head[N];
bool vis[N];
struct edge
{
    int nxt,to,c,w;
}e[M<<1];
/**/
inline void add(int u,int v,int c,int w)
{
    e[++cnt]=(edge){head[u],v,c,w};
    head[u]=cnt;
}
bool spfa()
{
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    queue<int>q;
    vis[s]=1,dis[s]=0;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();vis[u]=0;q.pop();
        for(int i=head[u];i;i=e[i].nxt)
        {
            int v=e[i].to;
            if(dis[v]>dis[u]+e[i].w&&e[i].c)
            {
                dis[v]=dis[u]+e[i].w;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return dis[t]!=inf;
}
int dfs(int u,int flow)
{
    if(u==t)
    {
        vis[t]=1;
        maxflow+=flow;
        return flow;
    }
    int used=0,minflow=0;
    vis[u]=1;
    for(int i=head[u];i;i=e[i].nxt)
    {
        int v=e[i].to;
        if((v==t||!vis[v])&&e[i].c&&dis[v]==dis[u]+e[i].w)
        {
            if((minflow=dfs(v,min(flow-used,e[i].c)))!=0)
            {
                mincost+=e[i].w*minflow;
                used+=minflow;
                e[i].c-=minflow;
                e[i^1].c+=minflow;
                if(used==flow) break;
            }
        }
    }
    return used;
}
void Dinic()
{
    while(spfa())
    {
        vis[t]=1;
        while(vis[t])
        {
            memset(vis,0,sizeof(vis));
            dfs(s,inf);
        }
    }
    cout<<maxflow<<' '<<mincost<<'\n';
}
int main()
{
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    read(n);read(m);read(s);read(t);
    for(int i=1,a,b,c,d;i<=m;i++)
    {
        read(a);read(b);read(c);read(d);
        add(a,b,c,d);
        add(b,a,0,-d);
    }
    Dinic();
}
Dinic最小费用最大流

 


 

END~

BY:楚泫

DATE:2019/3/13 19:46

 

posted @ 2019-03-13 19:47  楚泫  阅读(242)  评论(0编辑  收藏  举报