Dinic 学习笔记

前置知识

有关网络流基本概念

板子题

题目传送门
我们有一张图,图中有 n 个点 m 条边,要求从源点流向汇点的最大流量(可以有很多条路到达汇点),其实就是的最大流问题。

题目解析

首先我们想到,我们可以直接dfs,然后用掉这条路径上能用的最大的流量。
不难发现这种算法是错误的,接下来我们举一个反例:

如果运气不好我们就可能走 1234 ,答案是 1
但是我们发现正确答案是 124134 ,答案是 2

增广路

所以说我们需要采用一种反悔的方法,其实就是增广路。
增广路就是在我们用了一条边的流量之后,建一条反向边,边的流量大小就是减少的流量的大小。
但是为什么这样是正确的呢?
略证如下图:(转自OI Wiki)

EK

那么我们就可以直接通过bfs找增广路来做了。这种做法叫做EK,代码因为太难写我就不写了吧
以下代码和Tips来自OI Wiki
Tips

#define maxn 250
#define INF 0x3f3f3f3f
struct Edge {
int from, to, cap, flow;
Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};
struct EK {
int n, m; // n:点数,m:边数
vector<Edge> edges; // edges:所有边的集合
vector<int> G[maxn]; // G:点 x -> x 的所有边在 edges 中的下标
int a[maxn], p[maxn]; // a:点 x -> BFS 过程中最近接近点 x 的边给它的最大流
// p:点 x -> BFS 过程中最近接近点 x 的边
void init(int n) {
for (int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
int Maxflow(int s, int t) {
int flow = 0;
for (;;) {
memset(a, 0, sizeof(a));
queue<int> Q;
Q.push(s);
a[s] = INF;
while (!Q.empty()) {
int x = Q.front();
Q.pop();
for (int i = 0; i < G[x].size(); i++) { // 遍历以 x 作为起点的边
Edge& e = edges[G[x][i]];
if (!a[e.to] && e.cap > e.flow) {
p[e.to] = G[x][i]; // G[x][i] 是最近接近点 e.to 的边
a[e.to] =
min(a[x], e.cap - e.flow); // 最近接近点 e.to 的边赋给它的流
Q.push(e.to);
}
}
if (a[t]) break; // 如果汇点接受到了流,就退出 BFS
}
if (!a[t])
break; // 如果汇点没有接受到流,说明源点和汇点不在同一个连通分量上
for (int u = t; u != s;
u = edges[p[u]].from) { // 通过 u 追寻 BFS 过程中 s -> t 的路径
edges[p[u]].flow += a[t]; // 增加路径上边的 flow 值
edges[p[u] ^ 1].flow -= a[t]; // 减小反向路径的 flow 值
}
flow += a[t];
}
return flow;
}
};

它的算法复杂度为 O(nm2) ,显然有很大的提升空间。

Dinic

这样我们来到了Dinic,阅读以下内容请确保你理解了增广路,其实上面EK和EK的代码是为了让你更好理解增广路。

Dinic的算法流程如下:
在每次增广前,通过bfs将图分层,设源点的层数为 1 ,那么一个点的层数便是它离源点的最近距离。
这样我们就可以做以下两件事情:

  1. 如果不存在到汇点的增广路(即汇点的层数不存在),我们即可停止增广。
  2. 确保我们找到的增广路是最短的。
    接下来是dfs找增广路的过程。
    我们每次找增广路的时候,都只找比当前点层数多 1 的点进行增广(这样就可以确保我们找到的增广路是最短的)。

Dinic算法有两个优化:

  1. 多路增广:每次找到一条增广路的时候,我们可以利用残余部分流量,再找出一条增广路。这样就可以在一次dfs中找出多条增广路。
  2. 当前弧优化:一条边在一次找增广路中最多被增广一次,因为被增广一次之后就没有流量再次增广了。

时间复杂度: O(n2m)
证明
略证:汇点的层数一开始最小是 1 ,每次增广最少增加 1 层,总共有 n 个节点,所以我们最多增广 n 次。每次增广显然最坏复杂度为 O(nm)
Dinic的时间复杂度在稀疏图上和EK相当,但是在稠密图上远远胜过EK。
当然,我们很难让Dinic达到最坏复杂度,我们需要特殊构造数据,当然我不会构造

Update on 2021.8.17

更改了代码,(原来写假了)。
更改的地方:在 dfs 函数增加了 if(!sum) break; 这句话。

代码

(可以过模板题)
Tips:注意开long long

#include<queue>
#include<cstdio>
#include<cstring>
#define maxn 239
#define maxm 10039
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
//#define debug
typedef int Type;
typedef long long ll;
inline Type read(){
Type sum=0;
int flag=0;
char c=getchar();
while((c<'0'||c>'9')&&c!='-') c=getchar();
if(c=='-') c=getchar(),flag=1;
while('0'<=c&&c<='9'){
sum=(sum<<1)+(sum<<3)+(c^48);
c=getchar();
}
if(flag) return -sum;
return sum;
}
int n,m,s,t,u,v,w;
int head[maxn],to[maxm],nex[maxm],kkk=1,now[maxn];
ll c[maxm];
#define add(x,y,z) to[++kkk]=y;\
nex[kkk]=head[x];\
now[x]=head[x]=kkk;\
c[kkk]=z;
int dep[maxn];
queue<int> q,E;
int bfs(){
memset(dep,0,sizeof(dep));
for(int i=1;i<=n;i++) now[i]=head[i];
q=E; q.push(s); dep[s]=1;
while(!q.empty()){
int cur=q.front(); q.pop();
for(int i=head[cur];i;i=nex[i])
if(!dep[to[i]]&&c[i]>0){
dep[to[i]]=dep[cur]+1;
if(to[i]==t)
return 1;
q.push(to[i]);
}
}
return 0;
}
ll dfs(int x,ll sum){
if(x==t) return sum;
ll res=0,tmp;
for(int i=now[x];i&&sum>0;i=nex[i]){
now[x]=i;
if(dep[x]+1==dep[to[i]]&&c[i]>0){
tmp=dfs( to[i],min(c[i],sum) );
if(tmp==0) dep[to[i]]=0;
c[i]-=tmp; c[i^1]+=tmp; sum-=tmp; res+=tmp;
}
if(!sum) break;
}
return res;
}
ll ans=0;
int main(){
//freopen("P3376_2.in","r",stdin);
//freopen(".out","w",stdout);
n=read(); m=read(); s=read(); t=read();
for(int i=1;i<=m;i++){
u=read(); v=read(); w=read();
add(u,v,w); add(v,u,0);
}
while(bfs()) ans+=dfs(s,0x7fffffffffffffff);
printf("%lld",ans);
return 0;
}
posted @   jiangtaizhe001  阅读(49)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示