最大流及Dinic算法

参考博客:http://www.cnblogs.com/SYCstudio/p/7260613.html

https://comzyh.com/blog/archives/568/#Dinic-Code

https://www.cnblogs.com/y-clever/p/6308820.html

https://blog.csdn.net/lirewriter/article/details/78759337

https://www.bilibili.com/video/av18800207/index_2.html#page=2

网络流的最大流算法就是指的一个流量的方案使得网络中流量最大。

网络流最大流的求解:

网络流的所有算法都是基于一种增广路的思想,下面首先简要的说一下增广路思想,其基本步骤如下:

1.找到一条从源点到汇点的路径,使得路径上任意一条边的残量>0(注意是小于而不是小于等于,这意味着这条边还可以分配流量),这条路径便称为增广路
2.找到这条路径上最小的F(u)(v)(我们设F(u)(v)表示u->v这条边上的残量即剩余流量),下面记为flow
3.将这条路径上的每一条有向边u->v的残量减去flow,同时对于起反向边v->u的残量加上flow(为什么呢?我们下面再讲)
4.重复上述过程,直到找不出增广路,此时我们就找到了最大流

这个算法是基于增广路定理(Augmenting Path Theorem): 网络达到最大流当且仅当残留网络中没有增广路。

Dinic 算法:
基本思路:

1.根据残量网络计算层次图。
2.在层次图中使用DFS进行增广直到不存在增广路。
3.重复以上步骤直到无法增广。

较详细解释(流程) :

>

1.用BFS建立分层图 注意:分层图是以当前图为基础建立的,所以要重复建立分层图。
2.用DFS的方法寻找一条由源点到汇点的路径,获得这条路径的流量I 根据这条路径修改整个图,将所经之处正向边流量减少I,反向边流量增加I,注意I是非负数。
3.重复步骤2,直到DFS找不到新的路径时,重复步骤1。

当前弧优化:

对于每个点,我可能在一次BFS之后DFS多次。那么它出发的边所到的点里, 有些点出发已经满流。

这样, 我就可以每个点记录一个当前弧, 表示这次DFS它最后DFS到哪条弧,下次DFS它的时候就从这条弧开始。

这样,我就可以保证每条边在一次DFS中满流后不会再遍历。

模板:

typedef long long ll;
const long long INF=0x3f3f3f3f3f3f3f3f;
int n;
int s,e;//源点、汇点
int top;
struct edge
{
    int to;
    ll cap;
    int next;
}eg[200010];//邻接表要开边数的两倍
int head[10010];
int cur[10010];//当前弧优化
int dis[10010];//分层图中每个点的层数(即到原点的最短距离)
void add(int x,int y,ll z)
{
    eg[top].cap=z;
    eg[top].to=y;
    eg[top].next=head[x];
    head[x]=top++;
}
bool bfs()
{
    memset(dis,-1,sizeof(dis));
    queue<int>q;
    dis[s]=0;
    q.push(s);
    while(!q.empty())
    {
        int id=q.front();
        q.pop();
        for(int i=head[id];i!=-1;i=eg[i].next)
        {
            int temp=eg[i].to;
            if(dis[temp]==-1&&eg[i].cap>0)
            {
                dis[temp]=dis[id]+1;
                q.push(temp);
                if(temp==e)break;
            }
        }
    }
    return dis[e]!=-1;//能否到汇点,不能就结束
}
ll dfs(int id,ll cp)//id为当前节点,cp为当前最小残量
{
    if(id==e||cp==0)return cp;//流到终点了或者没有流了
    ll res=0,f;//ret:本次搜索能找到的所有流量,f:本次搜索一条路径的可以通过的流量
    for(int i=cur[id];i!=-1;i=eg[i].next)
    {
        int temp=eg[i].to;
        if(dis[temp]==dis[id]+1)
        {
            f=dfs(temp,min(cp-res,eg[i].cap));//能通的流就等于搜过后面之后允许的流,和自己允许的流的最小值
            if(f>0)
            {
                eg[i].cap-=f;
                eg[i^1].cap+=f;//反向边剩余流量加上f
                res+=f;
                if(res==cp)return cp;
            }
        }
    }
    if(res==0)dis[id]=-1;
    return res;
}
ll dinic()
{
    ll ans=0;
    while(bfs())//建立分层图
    {
        for(int i=1;i<=n;i++)
            cur[i]=head[i];//每一次建立完分层图后都要把cur置为每一个点的第一条边//???
        ans+=dfs(s,INF);//只要有路就走,假设它有无限流,再慢慢调整
    }
    return ans;
}
int main()
{
    int m;
    while(scanf("%d%d%d%d",&n,&m,&s,&e)!=EOF)
    {
        top=0;
        memset(head,-1,sizeof(head));
        while(m--)
        {
            int x,y;
            ll z;
            scanf("%d%d%lld",&x,&y,&z);
            add(x,y,z);
            add(y,x,0);
        }
        printf("%lld\n",dinic());
    }
    return 0;
}
最大流最小割定理

在一个有权图中,源点为Vs,汇点为Vt,如果拿掉这张图中的一些边,就无法从Vs到达Vt,这些边的组合就叫做割集。

对于一个网络流图G=(V,E),其割的定义为一种点的划分方式:将所有的点划分为S和T=V-S两个部分,其中源点s∈S,汇点t∈T。

对于一个割(S,T),我们定义净流f(S,T)表示穿过割(S,T)的流量之和,即:

f(S,T) = Σf(u,v) | u∈S,v∈T
举个例子:

1.jpg

净流f = f(2,4)+f(3,4)+f(3,5) = 12+(-4)+11 = 19

同时我们定义割的容量C(S,T)为所有从S到T的边容量之和,即:

C(S,T) = Σc(u,v) | u∈S,v∈T

割的容量:所有正向割边的容量和

同样在上面的例子中,其割的容量为:

c(2,4)+c(3,5)=12+14=26

实际上对于任意一个割的净流f(S,T)总是和网络流的流量f相等。比如上面例子中我们改变一下割的方式:

2.jpg

可以计算出对于这两种情况净流f(S,T)仍然等于19。

任意一个割的净流f(S,T)都等于当前网络的流量f。

对于网络的任意一个流f一定是小于等于任意一个割的容量C(S,T)。

而在所有可能的割中,存在一个容量最小的割,我们称其为最小割。

对于任一个网络流图来说,其最大流一定是小于等于最小割的。

最大流最小割定理:

对于一个网络流图G=(V,E),其中有源点s和汇点t,那么下面三个条件是等价的:

流f是图G的最大流

残留网络Gf不存在增广路

对于G的某一个割(S,T),此时f = C(S,T)

首先有割的容量C(S,T)=T中所有正权点的权值之和+S中所有负权点的权值绝对值之和

最小费用最大流

费用流建立在网络最大流的基础上,一张图中最大流有且仅有一个,但是最大流条数往往不止一条,这时候对于我们来说,可能要找出这些最大流中最小(或者最大)的那一条路径(贪心策略嘛),这就是最小(最大)费用最大流

typedef long long ll;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n;
int s,e;//源点、汇点
ll ans;//最大流量情况下的最小费用
int top;
struct edge
{
    int to;
    ll cap;
    ll cost;
    int next;
}eg[100010];//邻接表要开边数的两倍
int head[5010];
bool vis[5010];
ll dis[5010];
void add(int a,int b,ll c,ll d)
{
    eg[top].cap=c;
    eg[top].cost=d;
    eg[top].to=b;
    eg[top].next=head[a];
    head[a]=top++;
}
bool spfa(int ss,int ee)
{
    memset(vis,0,sizeof(vis));
    for(int i=0;i<=n;i++)
        dis[i]=INF;
    dis[ee]=0;vis[ee]=true;
    deque<int>q;
    q.push_back(ee);
    while(!q.empty())
    {
        int temp=q.front();
        q.pop_front();
        for(int i=head[temp];i!=-1;i=eg[i].next)
        {
            int tp=eg[i].to;
            if(eg[i^1].cap&&dis[tp]>dis[temp]-eg[i].cost)
            {
                dis[tp]=dis[temp]-eg[i].cost;
                if(!vis[tp])
                {
                    vis[tp]=true;
                    if(!q.empty()&&dis[tp]<dis[q.front()])q.push_front(tp);
                    else q.push_back(tp);
                }
            }
        }
        vis[temp]=false;
    }
    return dis[ss]<INF;
}
ll dfs(int x,ll cp)
{
    if(x==e)
    {
        vis[e]=true;
        return cp;
    }
    ll used=0,a;
    vis[x]=true;
    for(int i=head[x];i!=-1;i=eg[i].next)
    {
        int temp=eg[i].to;
        if(!vis[temp]&&eg[i].cap&&dis[x]-eg[i].cost==dis[temp])
        {
            a=dfs(temp,min(eg[i].cap,cp-used));
            if(a)
            {
                ans+=a*eg[i].cost;
                eg[i].cap-=a;
                eg[i^1].cap+=a;
                used+=a;
            }
            if(used==cp)break;
        }
    }
    return used;
}
ll costflow()
{
    ll flow=0;
    while(spfa(s,e))
    {
        vis[e]=1;
        while(vis[e])
        {
            memset(vis,0,sizeof(vis));
            flow+=dfs(s,INF);
        }
    }
    return flow;
}
int main()
{
    int m;
    while(scanf("%d%d%d%d",&n,&m,&s,&e)!=EOF)
    {
        top=0;
        memset(head,-1,sizeof(head));
        while(m--)
        {
            int a,b;
            ll c,d;
            scanf("%d%d%lld%lld",&a,&b,&c,&d);
            add(a,b,c,d);
            add(b,a,0,-d);
        }
        ans=0;
        ll temp=costflow();//最大流
        printf("%lld %lld\n",temp,ans);
    }
    return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

匹配问题专题 Previous
匹配问题 Next