网络流dinic与EK
题目链接:网络流
EK算法核心思想
EK算法是Ford-Fulkerson方法的一种实现,使用BFS来寻找增广路径,用于求解最大流问题。
算法步骤:
-
初始化
- 构建残量网络,初始时正向边容量为原容量,反向边容量为0
- 最大流初始为0
-
循环寻找增广路径
- 使用BFS在残量网络中寻找从源点s到汇点t的路径
- 记录路径上的最小容量(瓶颈容量)
-
更新残量网络
- 沿着找到的增广路径,正向边减去流量,反向边加上流量
- 将瓶颈容量加到最大流中
-
重复直到找不到增广路径
核心代码:
bool E_K_bfs(){memset(vis,-1,sizeof(vis));pre[s]=-1;vis[s]=1;myline.push(s); minroad[s]=INT_MAX;while(!myline.empty()){int u=myline.front();myline.pop();vis[u]=0;for(int i=head[u];i;i=q[i].link){int v=q[i].v,w=q[i].w;if(w){ // 只有残量>0的边才考虑if(~vis[v]) continue; // 已访问过则跳过minroad[v]=min(minroad[u],w); // 记录路径最小容量pre[v]=i; // 记录到达v的边myline.push(v);vis[v]=u; // 标记已访问 }}}if(vis[t]!=-1) return true; // 找到增广路径return false;
}void update(){int now=t;while(now!=s){int id=pre[now];q[id].w -= minroad[t]; // 正向边减去流量q[id^1].w += minroad[t]; // 反向边增加流量now = q[id^1].v; // 回溯到前一个节点}maxflow += minroad[t]; // 增加总流量
}
关键特点:
- 链式前向星存图:
cnt从1开始,方便用^1获取反向边 - 残量网络:正向边存剩余容量,反向边存已用容量
- BFS寻路:保证找到的是最短增广路径
- 反向边机制:允许"反悔",这是最大流算法的核心
时间复杂度:
- O(VE²),其中V是节点数,E是边数
- 每次BFS:O(E)
- 最多进行O(VE)次BFS
算法正确性保证:
- 增广路定理:当且仅当残量网络中不存在s-t路径时,当前流是最大流
- 最短路径增长:每次找到最短增广路,保证算法终止
- 反向边:确保能找到全局最优解
与相关算法对比:
| 算法 | 时间复杂度 | 特点 |
|---|---|---|
| EK | O(VE²) | 实现简单,适合稀疏图 |
| Dinic | O(V²E) | 分层图+多路增广,效率更高 |
| ISAP | O(V²E) | 改进的Dinic,常数更小 |
总结:EK算法通过不断在残量网络中寻找增广路径并更新流量,最终得到最大流。
#include<bits/stdc++.h>
#include<queue>
using namespace std;const int N=1000101;
int n,m,s,t,cnt=1;int head[N];
int vis[N];
//int vis[N];
int pre[N],minroad[N];
queue <int> myline;
struct edge{int v,w,link,u;
}q[N<<2];void put(int x,int y,int z){q[++cnt].v=y,q[cnt].u=x,q[cnt].w=z,q[cnt].link=head[x],head[x]=cnt;q[++cnt].v=x,q[cnt].u=y,q[cnt].w=0,q[cnt].link=head[y],head[y]=cnt;
}bool E_K_bfs(){memset(vis,-1,sizeof(vis));pre[s]=-1;vis[s]=1;myline.push(s); minroad[s]=INT_MAX;while(!myline.empty()){int u=myline.front();myline.pop();vis[u]=0;for(int i=head[u];i;i=q[i].link){// puts("check");int v=q[i].v,w=q[i].w;if(w){if(~vis[v]) continue; minroad[v]=min(minroad[u],w);pre[v]=i;myline.push(v);vis[v]=u; // printf("v:%dminroad:%d",v,minroad[v]);}}}//puts("check");if(vis[t]!=-1){ return true;}return false;
}int maxflow=0;
void update()
{
// puts("checkgood");int now=t;while(now!=s){int id=pre[now];q[id].w-=minroad[t];q[id^1].w+=minroad[t];now=q[id^1].v;}maxflow+=minroad[t];
}
int main(){//freopen("tt.txt","r",stdin);//freopen("tt.out","w",stdout);scanf("%d%d%d%d",&n,&m,&s,&t);for(int i=1;i<=m;i++){int u,v,w;scanf("%d%d%d",&u,&v,&w);put(u,v,w);}while(E_K_bfs()) update();printf("%d",maxflow);
}
Dinic算法核心思想
Dinic算法是分层图+多路增广的网络流算法,比EK算法效率更高。
算法步骤:
-
BFS构建分层图
- 从源点s开始进行BFS
- 记录每个节点到源点的最短距离(层数)
- 只考虑残量>0的边
-
DFS多路增广
- 在分层图上进行DFS
- 只向下一层的节点推进
- 一次DFS可以找到多条增广路
-
重复直到无法构建分层图
核心代码:
bool bfs(){for(int i=1;i<=n;i++) deep[i]=MAX+5,vis[i]=0,cur[i]=head[i];deep[s]=0;while(!myline.empty()) myline.pop();myline.push(s);vis[s]=1;while(!myline.empty()){int u=myline.front();myline.pop();for(int i=head[u];i;i=q[i].link){int v=q[i].v;ll w=q[i].w;if(vis[v]||(!w)) continue; // 已访问或残量为0if(deep[v]>MAX){ // 未访问过deep[v]=deep[u]+1; // 记录层数myline.push(v);vis[v]=1;}}}if(deep[t]<MAX) return true; // 能到达汇点return false;
}
BFS作用:构建分层图,deep[i]表示节点i的层数
int dfs(ll limit,int st){if(!limit||st==t) return limit; // 无流量或到达汇点ll flow=0,f;for(int i=cur[st];i;i=q[i].link){cur[st]=i; // 当前弧优化int v=q[i].v;ll w=q[i].w;if(deep[v]==deep[st]+1&&(f=dfs(min(limit,w),v))){flow+=f;limit-=f;q[i].w-=f;q[i^1].w+=f;if(limit<0) return limit;}}return flow;
}
DFS特点:
- 多路增广:一次DFS可能找到多条路径
- 当前弧优化:
cur[st]避免重复检查无效边 - 分层限制:
deep[v]==deep[st]+1确保向下一层推进
Dinic算法的关键优化:
- 分层图:避免DFS绕远路
- 多路增广:一次DFS找到多条增广路
- 当前弧优化:避免重复检查已经"榨干"的边
时间复杂度:
- O(V²E),比EK的O(VE²)更优
- 对于单位容量图: O ( m i n ( V 2 / 3 , E 1 / 2 ) × E ) O(min(V^{2/3}, E^{1/2}) × E) O(min(V2/3,E1/2)×E)
与EK算法对比:
| 特性 | EK算法 | Dinic算法 |
|---|---|---|
| 寻路方式 | BFS单路增广 | BFS分层 + DFS多路增广 |
| 时间复杂度 | O(VE²) | O(V²E) |
| 实际效率 | 较慢 | 快很多 |
| 代码复杂度 | 简单 | 中等 |
代码中的关键点:
- 链式前向星:
cnt从1开始,方便反向边操作 - 当前弧优化:
cur[i]数组,避免重复检查 - 分层图:
deep[i]记录节点层数 - 多路增广:DFS中
flow+=f累计多条路径
算法正确性保证:
- 分层图性质:确保找到的增广路是最短的
- 多路增广:充分利用每次BFS构建的分层图
- 当前弧优化:不影响正确性,只提升效率
总结:Dinic算法通过分层图限制DFS方向,配合多路增广和当前弧优化,大幅提升了网络流算法的效率
#include<bits/stdc++.h>
using namespace std;typedef long long ll;
const int N=1e4+5,M=1e5+5;
const ll MAX=123456789000;
int n,m,s,t;ll w;
queue<int > myline;
int head[N],cur[N],cnt=1;
ll deep[N];
struct edge{int link,v,u;ll w;
}q[M<<1];
bool vis[N];
void put(int u,int v,ll w){q[++cnt].v=v,q[cnt].u=u,q[cnt].w=w;q[cnt].link=head[u],head[u]=cnt;}
bool bfs(){for(int i=1;i<=n;i++) deep[i]=MAX+5,vis[i]=0,cur[i]=head[i];deep[s]=0;while(!myline.empty()) myline.pop();myline.push(s);vis[s]=1;while(!myline.empty()){int u=myline.front();myline.pop();for(int i=head[u];i;i=q[i].link){int v=q[i].v;ll w=q[i].w;if(vis[v]||(!w)) continue;if(deep[v]>MAX){deep[v]=deep[u]+1;myline.push(v);vis[v]=1;}}}if(deep[t]<MAX) return true;return false;
}
int dfs(ll limit,int st){if(!limit||st==t) return limit;ll flow=0,f;for(int i=cur[st];i;i=q[i].link){cur[st]=i;int v=q[i].v;ll w=q[i].w;if(deep[v]==deep[st]+1&&(f=dfs(min(limit,w),v))){flow+=f;limit-=f;q[i].w-=f;q[i^1].w+=f;if(limit<0) return limit;}}return flow;
}
void Dinic(){ll ff=0;while(bfs()){
// for(int i=1;i<=n;i++){
// printf("deep[%d]=%lld ",i,deep[i]);
// }
// puts("");ff=ff+dfs(MAX,s);} printf("%lld",ff);
}
int main(){scanf("%d%d%d%d",&n,&m,&s,&t);for(int i=1;i<=m;i++){int u,v;ll w;scanf("%d%d%lld",&u,&v,&w);put(u,v,w),put(v,u,0);}Dinic();
}
