Dinic & 当前弧优化

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最大流的概念由此而来:

    为了解决我们上面遇到的低效方法,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

 

 

 

你可能感兴趣的:(ac,图论)