Dinic的核心思想就是先跑一遍Bfs BfsBfs,为每个位置设置一个高度,一个点的高度为前一个点+1 +1+1(如果某一条边已经满流则不设置)
为什么非得要用bfs建立分层图 解释:
虽然说我们已经知道了增广路的实现,但是单纯地这样选择可能会陷入不好的境地,比如说这个经典的例子:
我们一眼可以看出最大流是999(s->v->t)+999(s->u->t),但如果程序采取了不恰当的增广策略:s->v->u->t
我们发现中间会加一条u->v的边
而下一次增广时:
若选择了s->u->v->t
然后就变成
这样一共会跑多少遍呢?是inf∗2 inf*2inf∗2遍,对于一个把inf infinf设成1010580540 10105805401010580540的人来说,这个复杂度是完全不能接受的!!所以我们加入一个Bfs Bfs过程,在设置高度的同时能够确认这个网络还能不能跑到汇点。
为了解决我们上面遇到的低效方法,Dinic算法引入了一个叫做分层图的概念。
具体就是对于每一个点,我们根据从源点开始的bfs序列,为每一个点分配一个深度。
然后我们进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。
建边方法:
虽然网络流的图看着是单向的,但我们在设置时应引入残流网络的概念,即对于每条边,都要设置一条反向的边。
原来的建图过程为:
AddEdge(from,to,flow);
现在为:
AddEdge(from,to,flow);
AddEdge(to,from,0);
一些变量的定义:
int s,t;//源点和汇点
int cnt;//边的数量,从0开始编号。
int Head[maxN];//每一个点最后一条边的编号
int Next[maxM];//指向对应点的前一条边
int V[maxM];//每一条边指向的点
int W[maxM];//每一条边的残量
int Depth[maxN];//分层图中标记深度
Dinic主过程:
int Dinic()
{
int Ans=0;//记录最大流量
while (bfs())
{
while (int d=dfs(s,inf))
Ans+=d;
}
return Ans;
}
bfs分层图过程:
bool bfs()
{
queue Q;//定义一个bfs寻找分层图时的队列
while (!Q.empty())
Q.pop();
memset(Depth,0,sizeof(Depth));
Depth[s]=1;//源点深度为1
Q.push(s);
do
{
int u=Q.front();
Q.pop();
for (int i=Head[u];i!=-1;i=Next[i])
if ((W[i]>0)&&(Depth[V[i]]==0))//若该残量不为0,且V[i]还未分配深度,则给其分配深度并放入队列
{
Depth[V[i]]=Depth[u]+1;
Q.push(V[i]);
}
}
while (!Q.empty());
if (Depth[t]==0)//当汇点的深度不存在时,说明不存在分层图,同时也说明不存在增广路
return 0;
return 1;
}
dfs寻找增广路过程
int dfs(int u,int dist)//u是当前节点,dist是当前流量
{
if (u==t)//当已经到达汇点,直接返回
return dist;
for (int i=Head[u];i!=-1;i=Next[i])
{
if ((Depth[V[i]]==Depth[u]+1)&&(W[i]!=0))//注意这里要满足分层图和残量不为0两个条件
{
int di=dfs(V[i],min(dist,W[i]));//向下增广
if (di>0)//若增广成功
{
W[i]-=di;//正向边减
W[i^1]+=di;反向边加
return di;//向上传递
}
}
}
return 0;//否则说明没有增广路,返回0
}
未优化前的dinic最大流:
class Graph
{
private:
int s,t;
int cnt;
int Head[maxN];
int Next[maxM];
int V[maxM];
int W[maxM];
int Depth[maxN];
public:
int n;
void init(int nn,int ss,int tt)//初始化
{
n=nn;
s=ss;
t=tt;
cnt=-1;
memset(Head,-1,sizeof(Head));
memset(Next,-1,sizeof(Next));
return;
}
void _Add(int u,int v,int w)
{
cnt++;
Next[cnt]=Head[u];
V[cnt]=v;
W[cnt]=w;
Head[u]=cnt;
}
void Add_Edge(int u,int v,int w)//加边,同时加正向和反向的
{
_Add(u,v,w);
_Add(v,u,0);
}
int dfs(int u,int dist)
{
//cout<<"Dfs:"<0)
{
W[i]-=di;
W[i^1]+=di;
return di;
}
}
}
return 0;
}
int bfs()
{
//cout<<"Bfs.begin:"< Q;
while (!Q.empty())
Q.pop();
memset(Depth,0,sizeof(Depth));
Depth[s]=1;
Q.push(s);
do
{
int u=Q.front();
//cout<0)&&(Depth[V[i]]==0))
{
Depth[V[i]]=Depth[u]+1;
Q.push(V[i]);
}
}
}
while (!Q.empty());
//cout<<"Bfs.end"<0)
return 1;
return 0;
}
int Dinic()
{
int Ans=0;
while (bfs())
{
while (int d=dfs(s,inf))
Ans+=d;
}
return Ans;
}
};
现在讲一下当前弧优化:
我们知道,在寻找增广路的过程中,如果找到了,那么会直接先朝增广路流过去,一直流到汇点。
在这个过程中,必然有一条边达到了满流,即流不能再经过这条边,那么我们找到的该条增广路也就不能跑了,但是在下一次增广的过程中,如果再次到达这个点,而找到了不能增广的路进行增广(因为第一次找到的增广路可能还有流量,而无法到达汇点),就会徒增很多的时间复杂度。
所以我们引入一个数组cur[] ,记录每个点下一次该走的边(因为一个点可能有多条边可走,而在前面的一些边可能已经把当前流量花光,而且那些边也不再能增广,流量花光就直接退出了循环),这样就能省下不少的时间复杂度。
即每一次dfs增广时不从第一条边开始,而是用一个数组cur记录点u之前循环到了哪一条边,以此来加速
class Graph
{
private:
int cnt;
int Head[maxN];
int Next[maxM];
int W[maxM];
int V[maxM];
int Depth[maxN];
int cur[maxN];//cur就是记录当前点u循环到了哪一条边
public:
int s,t;
void init()
{
cnt=-1;
memset(Head,-1,sizeof(Head));
memset(Next,-1,sizeof(Next));
}
void _Add(int u,int v,int w)
{
cnt++;
Next[cnt]=Head[u];
Head[u]=cnt;
V[cnt]=v;
W[cnt]=w;
}
void Add_Edge(int u,int v,int w)
{
_Add(u,v,w);
_Add(v,u,0);
}
int dfs(int u,int flow)
{
if (u==t)
return flow;
for (int& i=cur[u];i!=-1;i=Next[i])//注意这里的&符号,这样i增加的同时也能改变cur[u]的值,达到记录当前弧的目的
{
if ((Depth[V[i]]==Depth[u]+1)&&(W[i]!=0))
{
int di=dfs(V[i],min(flow,W[i]));
if (di>0)
{
W[i]-=di;
W[i^1]+=di;
return di;
}
}
}
return 0;
}
int bfs()
{
queue Q;
while (!Q.empty())
Q.pop();
memset(Depth,0,sizeof(Depth));
Depth[s]=1;
Q.push(s);
do
{
int u=Q.front();
Q.pop();
for (int i=Head[u];i!=-1;i=Next[i])
if ((Depth[V[i]]==0)&&(W[i]>0))
{
Depth[V[i]]=Depth[u]+1;
Q.push(V[i]);
}
}
while (!Q.empty());
if (Depth[t]>0)
return 1;
return 0;
}
int Dinic()
{
int Ans=0;
while (bfs())
{
for (int i=1;i<=n;i++)//每一次建立完分层图后都要把cur置为每一个点的第一条边 感谢@青衫白叙指出这里之前的一个疏漏
cur[i]=Head[i];
while (int d=dfs(s,inf))
{
Ans+=d;
}
}
return Ans;
}
};
copy的这两位大神的:DKACVenus和SYCstudio