网络最大流模板 当前弧优化

学习参考:


1、用DFS实现的FF:代码-DFS实现,里面flag数组用来去重边。复杂度 O ( m f ) O(mf) O(mf):①每次DFS最多遍历所有的边且每条边最多遍历两次,所以一次DFS复杂度 O ( m ) O(m) O(m) ;②每次DFS至少找到一条流 f = 1 f=1 f=1 的路径。

2、用BFS实现的FF(即EK算法):代码-BFS实现 ,需要记录下路径。 复杂度 O ( n m 2 ) O(nm^2) O(nm2):①同上,每次BFS复杂度 O ( m ) O(m) O(m) ;②若增广路p的残留容量等于边 ( u , v ) (u,v) (u,v)的残留容量,则称边 ( u , v ) (u,v) (u,v)是增广路p的关键边。 ( u , v ) (u,v) (u,v)每次作为关键边时,两点的深度 d ′ [ u ] 、 d ′ [ v ] d'[u]、d'[v] d[u]d[v] 和前一次作为关键边时的深度 d [ u ] 、 d [ v ] d[u]、d[v] d[u]d[v] 相比总是严格递增的,所以 边数 × × ×每条边最多作为关键边的次数=增广次数 O ( n m ) O (nm) O(nm)详见

3、BFS+DFS实现:先用BFS分层,再用DFS多路增广(DFS过程中,只往层数高的地方走),一直重复这个步骤。复杂度 O ( n 2 m ) O(n^2m) O(n2m)

  • 多路增广:在某点DFS找到一条增广路后,如果还剩下多余的流量未用,继续在该点DFS尝试找到更多增广路。
  • 当前弧优化:如下图,当我们从s出发,遍历到左边两条边时当前最小的流量分别为12、2。我们从下面的那条边遍历过p点回溯后图中每条边中的残量(没有看反向边)如右图所示。那么我们第二次到p点时就可以直接从 与p点相连的第三条边开始找增广路,因为前面遍历过的两条边要么流量已经用完了,要么直接不能到汇点t。
    在这里插入图片描述

复杂度分析: 来自万能百科 因为在Dinic的执行过程中,每次重新分层,汇点所在的层次是严格递增的(每次dfs都会找到最短的增广路,以及长度和它相同的其他增广路),而n个点的层次图最多有n层,所以最多重新分层n次。在同一个层次图中,因为每条增广路都有一个瓶颈,而两次增广的瓶颈不可能相同(即上面提到的关键边不可能相同),所以增广路最多m条。搜索每一条增广路时,前进和回溯都最多n次,所以这两者造成的时间复杂度是O(nm);而沿着同一条边(i,j)不可能枚举两次,因为第一次枚举时要么这条边的容量已经用尽,要么点j到汇不存在通路从而可将其从这一层次图中删除。综上所述,Dinic算法时间复杂度的理论上界是 O ( n 2 m ) O(n^2m) O(n2m)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<iomanip>
using namespace std;
typedef long long LL;
const int manx=1e4+10;
const int manx2=1e7+10;
const int mod=1e9+7;
const LL INF=1e10;
const double inf=1e9+7;

int n,m,cou=2,s,t;
int deep[manx],flag[manx][manx];//deep记录每个点的深度
int head[manx],cur[manx];//cur数组用于实现当前弧优化
LL flow[manx];
struct node
{
    int e,bf;
    LL w;
}edge[manx];
inline int add(int s,int e,LL w)
{
    edge[cou]=node{e,head[s],w};
    head[s]=cou++;
    return cou-1;
}
int bfs()//bfs分层
{
    for(int i=0;i<=n;i++)cur[i]=head[i];//每次dfs之前复制一次head数组
    memset(deep,-1,sizeof deep);
    queue<int>qu;
    qu.push(s);
    deep[s]=0;
    while(!qu.empty())
    {
        int now=qu.front();
        qu.pop();
        for(int i=head[now];~i;i=edge[i].bf)
        {
            int net=edge[i].e;
            if(edge[i].w>0&&deep[net]==-1)
            {
                deep[net]=deep[now]+1;
                qu.push(net);
                //if(deep[net]==deep[t]+1)return 1;
                //其实遍历到汇点的下一层就可以退出了,这时候dfs到达汇点会经过的点已经更新完了
                //每次dfs找的增广路径长度应该都是一样的吧。。?
            }
        }
    }
    return deep[t]!=-1;
}
LL dfs(int now=s,LL flow=INF)
{
    if(!flow||now==t)
        return flow;
    LL ans=0;
    for(int i=cur[now];~i&&flow;i=edge[i].bf)
    {
        cur[now]=i;//“榨取”到编号为i这条边
        int net=edge[i].e;
        if(edge[i].w>0&&deep[net]==deep[now]+1)//只向更深处遍历
        {
            LL temp=dfs(net,min(flow,edge[i].w));
            flow-=temp,ans+=temp;//ans维护能从这条边(到now这点的边)流出的最大流量
            edge[i].w-=temp;//flow维护这条边的残量大小
            edge[i^1].w+=temp;//处理反向边
            //还有这个^1的操作很灵性啊哈哈哈哈
        }
    }
    return ans;
}
LL Dinic()
{
    LL ans=0;
    while(bfs())
        ans+=dfs();
    return ans;
}
int main()
{
    int u,v;
    LL w;
    memset(head,-1,sizeof head);
    scanf("%d%d%d%d",&n,&m,&s,&t);
    while(m--)
    {
        scanf("%d%d%lld",&u,&v,&w);
        if(flag[u][v])//去重边
        {
            edge[flag[u][v]].w+=w;
            continue;
        }
        flag[u][v]=add(u,v,w);
        add(v,u,0);
    }
    printf("%lld\n",Dinic());
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值