学习参考:
- 视频讲解: 网络流基础:理解最大流/最小割定理 (蒋炎岩)
- Ford-Fulkerson方法 -> EK算法 -> Dinic 及优化:算法学习笔记(28): 网络流
- Dinic + 当前弧优化 教程及模板
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;
}