图论 —— 网络流 —— 最大流 —— Dinic 算法
【概述】
Dinic 算法在 EK 算法的基础上进行了优化,其时间复杂度为 O(n*n*m)。
Dinic 在找增广路的时也是找最短增广路, 但与 EK 算法不同的是 Dinic 算法并不是每次 bfs 只找一个增广路,他会首先通过一次 bfs 为所有点添加一个标号,构成一个层次图,然后在层次图中寻找增广路进行更新。
【基本思想】
1.初始化容量网络与网络流
2.构造残量网络,根据残留网络通过 BFS 计算层次网络,若汇点不在层次图中(汇点层次为 -1),则结束
3.在层次网络中使用一次 DFS 进行增广,DFS 执行完毕,该阶段的增广也执行完毕
4.转步骤 2
【求最大流】
1.一般方式
使用邻接矩阵的形式,适用于点较少的情况。
int n,m;//n个点m条边
int S,T;//源点、汇点
int level[N];//存储结点分层层数
struct Edge{
int cap;
int flow;
}edge[N][N];
bool bfs(){//构造层次网络
memset(level,0,sizeof(level));
queue<int> Q;
Q.push(S);
level[S]=1;
while(!Q.empty()){
int x=Q.front();
Q.pop();
for(int y=1;y<=n;y++){
if(!level[y]&&edge[x][y].cap>edge[x][y].flow){
level[y]=level[x]+1;
Q.push(y);
}
}
}
return level[T]!=0;
}
int dfs(int x,int cp){//计算可增加流量
if(x==n)
return cp;
int flow=cp;//记录从x到t的最小残量
for(int y=1;y<=n;y++){
if(level[x]+1==level[y]){
if(edge[x][y].cap>edge[x][y].flow){
int minn=min(flow,edge[x][y].cap-edge[x][y].flow);
int newFlow=dfs(y,minn);
edge[x][y].flow+=newFlow;
edge[y][x].flow-=newFlow;
flow-=newFlow;
}
}
if(flow==0)
break;
}
return cp-flow;
}
int dinic(){
int flow=0;
int tf=0;
while(bfs()){
while(tf=dfs(1,INF)){
flow+=tf;
}
}
return flow;
}
int main(){
scanf("%d%d",&n,&m);
memset(edge,0,sizeof(edge));
while(m--){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
edge[x][y].cap+=w;//便于处理重边
}
S=1,T=n;
printf("%d\n",dinic());
return 0;
}
2.vector 邻接表实现
使用 vector 邻接表与结构体结合,适用于点较多的情况,但速度稍慢于邻接矩阵。
struct Edge{
int from,to;
int cap,flow;
Edge(){}
Edge(int from,int to,int cap,int flow):from(from),to(to),cap(cap),flow(flow){}
};
int n,m; //结点数,边数(含反向弧)
int S,T; //源点、汇点
vector<Edge> edges; //边表,edges[e]和edges[e^1]互为反向弧
vector<int> G[N]; //邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
bool vis[N]; //BFS使用,标记一个节点是否被遍历过
int dis[N]; //dis[i]表从起点s到i点的距离(层次)
int cur[N]; //cur[i]表当前正访问i节点的第cur[i]条弧
void addEdge(int from,int to,int cap){
edges.push_back( Edge(from,to,cap,0) );
edges.push_back( Edge(to,from,0,0) );
int m=edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool BFS(){//构建层次网络
memset(vis,0,sizeof(vis));
dis[S]=0;
vis[S]=true;
queue<int> Q;//用来保存节点编号
Q.push(S);
while(!Q.empty()){
int x=Q.front();
Q.pop();
for(int y=0;y<G[x].size();y++){
Edge& e=edges[G[x][y]];
if(!vis[e.to] && e.cap>e.flow){
vis[e.to]=true;
dis[e.to]=dis[x]+1;
Q.push(e.to);
}
}
}
return vis[T];
}
int DFS(int x,int cp){//cp表示从s到x目前为止所有弧的最小残量
if(x==T || cp==0)
return cp;
int flow=0,newFlow;//flow用来记录从x到t的最小残量
for(int &y=cur[x];y<G[x].size();y++){
Edge &e=edges[G[x][y]];
if(dis[x]+1==dis[e.to]){
int minn=min(cp,e.cap-e.flow);
newFlow=DFS(e.to,minn);
if(newFlow>0){
e.flow+=newFlow;
edges[G[x][y]^1].flow-=newFlow;
flow+=newFlow;
cp-=newFlow;
if(cp==0)
break;
}
}
}
return flow;
}
int Dinic(){
int flow=0;
while(BFS()){
memset(cur,0,sizeof(cur));
flow+=DFS(S,INF);
}
return flow;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);//节点编号从1到n,边编号从0到m-1
for(int i=1;i<=n;i++)
G[i].clear();
edges.clear();
for(int i=0;i<m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addEdge(u,v,w);
}
S=1,T=n;
printf("%d\n",Dinic());
return 0;
}
【求最小割】
使用邻接数组与 STL 中的 set 容器,在求最大流的过程中,将最小割求出。
其原理是最大流最小割定理:网络的最大流的流量等于最小割的容量
struct Edge{
int from,to;
int cap,flow;
Edge(){}
Edge(int from,int to,int cap,int flow):from(from),to(to),cap(cap),flow(flow){}
};
int n,m; //结点数,边数(含反向弧)
int S,T; //源点、汇点
vector<Edge> edges; //边表,edges[e]和edges[e^1]互为反向弧
vector<int> G[N]; //邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
bool vis[N]; //BFS使用,标记一个节点是否被遍历过
int dis[N]; //dis[i]表从起点s到i点的距离(层次)
int cur[N]; //cur[i]表当前正访问i节点的第cur[i]条弧
set<int> cutSet; //最小割
bool flag; //是否求最小割
void addEdge(int from,int to,int cap){
edges.push_back( Edge(from,to,cap,0) );
edges.push_back( Edge(to,from,0,0) );
int m=edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool BFS(){//构建层次网络
memset(vis,0,sizeof(vis));
dis[S]=0;
vis[S]=true;
//将超级源点加入最小割
if(flag)
cutSet.insert(S);
queue<int> Q;//用来保存节点编号
Q.push(S);
while(!Q.empty()){
int x=Q.front();
Q.pop();
for(int y=0;y<G[x].size();y++){
Edge& e=edges[G[x][y]];
if(!vis[e.to] && e.cap>e.flow){
vis[e.to]=true;
dis[e.to]=dis[x]+1;
Q.push(e.to);
if(flag)//记录最小割元素
cutSet.insert(e.to);
}
}
}
return vis[T];
}
int DFS(int x,int cp){//cp表示从s到x目前为止所有弧的最小残量
if(x==T || cp==0)
return cp;
int flow=0,newFlow;//flow用来记录从x到t的最小残量
for(int &y=cur[x];y<G[x].size();y++){
Edge &e=edges[G[x][y]];
if(dis[x]+1==dis[e.to]){
int minn=min(cp,e.cap-e.flow);
newFlow=DFS(e.to,minn);
if(newFlow>0){
e.flow+=newFlow;
edges[G[x][y]^1].flow-=newFlow;
flow+=newFlow;
cp-=newFlow;
if(cp==0)
break;
}
}
}
return flow;
}
int Dinic(){
int flow=0;
while(BFS()){
memset(cur,0,sizeof(cur));
flow+=DFS(S,INF);
}
return flow;
}
int main(){
scanf("%d%d",&n,&m);
while(m--){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
addEdge(x,y,w);
}
S=1,T=n;
flag=false;//不统计最小割所属元素
int minCut=Dinic();//计算最小割的值
printf("The Min-Cut and Max-Flow:%d\n",minCut);
flag=true;//统计最小割所属元素
BFS();//构建层次网络
printf("The Number of Min Cut:%d\n",cutSet.size());//最小割个数
printf("The Min Cut Set:");
set<int>::iterator it=cutSet.begin();
for(;it!=cutSet.end();it++)//最小割元素
printf("%d ",*it);
printf("\n");
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤