Dinic 学习笔记
前置知识
板子题
题目传送门
我们有一张图,图中有 个点 条边,要求从源点流向汇点的最大流量(可以有很多条路到达汇点),其实就是的最大流问题。
题目解析
首先我们想到,我们可以直接dfs,然后用掉这条路径上能用的最大的流量。
不难发现这种算法是错误的,接下来我们举一个反例:
如果运气不好我们就可能走 ,答案是 。
但是我们发现正确答案是 和 ,答案是 。
增广路
所以说我们需要采用一种反悔的方法,其实就是增广路。
增广路就是在我们用了一条边的流量之后,建一条反向边,边的流量大小就是减少的流量的大小。
但是为什么这样是正确的呢?
略证如下图:(转自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; } };
它的算法复杂度为 ,显然有很大的提升空间。
Dinic
这样我们来到了Dinic,阅读以下内容请确保你理解了增广路,其实上面EK和EK的代码是为了让你更好理解增广路。
Dinic的算法流程如下:
在每次增广前,通过bfs将图分层,设源点的层数为 ,那么一个点的层数便是它离源点的最近距离。
这样我们就可以做以下两件事情:
- 如果不存在到汇点的增广路(即汇点的层数不存在),我们即可停止增广。
- 确保我们找到的增广路是最短的。
接下来是dfs找增广路的过程。
我们每次找增广路的时候,都只找比当前点层数多 的点进行增广(这样就可以确保我们找到的增广路是最短的)。
Dinic算法有两个优化:
- 多路增广:每次找到一条增广路的时候,我们可以利用残余部分流量,再找出一条增广路。这样就可以在一次dfs中找出多条增广路。
- 当前弧优化:一条边在一次找增广路中最多被增广一次,因为被增广一次之后就没有流量再次增广了。
时间复杂度:
证明
略证:汇点的层数一开始最小是 ,每次增广最少增加 层,总共有 个节点,所以我们最多增广 次。每次增广显然最坏复杂度为 。
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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· 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工具