Dinic算法
普通的dinic算法在有些时候会被卡掉,因为每次dfs太多了,所以对此就有了当前弧优化来解决这个问题。
什么是当前弧优化
当前弧优化就是说我们在每次dfs找的时候,把已经榨干的点删掉,我们直接从可以增加流量的边开始。
假设我们的残留网络已经更新成现在这个网络了(如上图),假设源点s出发的有编号1、2、3号边,1和2号边都无法再增加流量了,没有用了(相当于这两条边被榨干了),那么我们在下次bfs得到的分层图再dfs找增广路时,就不考虑1和2号边了,直接从s出发去3号边找。就能省略很多层递归。
当前弧优化的实现
那么如何实现上述操作呢,我们加一个cur[ ]数组来帮助实现。cur数组最初就是h数组,就是邻接表存图的头指针。当我们在dfs时,从u点出发访问到了第i条边,就说明前i-1条边都已经被榨干了,所以才到了第i条边,因此我们直接改cur[u] = i,下次访问从u点出发的边时,就会直接访问第i条边,省略了前面i-1条边。
不懂的话看看下面代码模板,就会理解了。
代码模板
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 10010,M = 200010,INF = 0x3f3f3f3f; //N是点数,M是边数,INF是无穷大
int n,m,s,t,dis[N],cur[M]; //dis是存每个点的层数,cur是当前弧优化的头指针数组
int e[M],ne[M],w[M],h[M],idx; //链式前向星建图
void add(int a,int b,int c) //加边函数
{
e[idx] = b;
w[idx] = c; //w是残留网络的残量
ne[idx] = h[a];
h[a] = idx++;
}
bool bfs()
{
queue<int> q;
memset(dis,-1,sizeof dis); //dis初始化为-1
q.push(s);
dis[s] = 0; //s的层数记为0
cur[s] = h[s]; //cur的初始化和h一样,也可以在开始的时候把h复制一份
while(q.size())
{
int u = q.front();
q.pop();
for(int i = h[u];~i;i = ne[i])
{
int v = e[i];
if(dis[v] == -1 && w[i]) //如果v点没有被访问过,且残量为正
{
dis[v] = dis[u] + 1; //v的层数是u的层数+1
cur[v] = h[v]; //复制一份h给cur
if(v == t) //如果找到了t,说明存在增广路
return 1; //返回1
q.push(v); //v点入队
}
}
}
return 0; //未找到t点说明不存在增广路了,返回0
}
int dfs(int u,int limit) //u是当前点,limit是u点还可以分给后面点的最大流量
{
if(u == t) //如果递归到了汇点t,返回limit
return limit;
int flow = 0; //flow是从u点已经分出去的流量,最初是0
for(int i = cur[u];~i;i = ne[i]) //循环u的所有出边,注意这里用的是cur数组,就是当前弧优化,访问所有还可以扩大流量的出边
{
cur[u] = i; //更新cur,循环到i说明u点已经用到了第i条边,也说明前i-1条边已经被榨干了(所以才用到了第i条边),因此下次直接从u点用第i条边就可以了,所以cur[u]更新为i就可以了
int v = e[i];
if(dis[v] == dis[u]+1 && w[i]) //如果v点是u点的下一层,且残量为正,说明是一条增广路
{
int minf = dfs(v,min(w[i],limit-flow)); //递归v点,流给v点的流量是该边的容量和剩余的总流量limit-flow的最小值,minf是返回这条增广路上能增广的量
w[i] -= minf; //更新残留网络
w[i^1] += minf;
flow += minf; //更新分出去的流量
if(flow == limit) //当前弧优化的细节,如果分出去的流量flow和最大流量limit相等了,说明所有流量全部分出去了,不可能再分了,直接返回
return flow;
}
}
return flow; //return分出去的最大流量flow
}
int dinic()
{
int ans = 0;
while(bfs()) //当存在增广路时,bfs得到分层图
ans += dfs(s,INF); //dfs
return ans;
}
int main()
{
cin >> n >> m >> s >> t;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin >> a >> b >> c;
add(a,b,c); //建残留网络
add(b,a,0);
}
cout << dinic() << endl;
return 0;
}