Network flow, shortest path .dfs maximum flow, space travel

Reprinted from https://www.cnblogs.com/rmy020718/p/9546071.html

We introduce an algorithm to a classic problem.

Village where you opened a new underground water pipes, water plant to provide a steady stream of water, the villagers with water directly or indirectly, water, waste water and run out of the village to another uniform recycling point (set to discharge all the water recycling). Of course, each pipe has a certain capacity, waste water station to obtain the maximum number of water can converge?

Of course, this is a directed graph.

First, clear a few concepts:

Capacity: Each side has a capacity (maximum flow capacity of the pipe)

Source: the starting point (water).

Meeting Point: End Point (waste station).

Streams: a stream called a legal solution, i.e. be a valid path from a source to a sink.

Flow rate: the number of times each through each side of its flow is referred to, the total number of the flow eventually collected for the entire stream.

 

The figures are there so few restrictions:

Capacity constraints: each edge flow does not exceed its capacity (water pipes will burst).

Flow balance: to a point other than the point source and sink, its inflow equals outflow constant.

 

Now, let's simplify this figure, to solve this problem.

x / y represents the total flow y, the stream has a x.

First, we would expect to find a random path, but if walked shown above.

When completed, 1-> 2-> 3-> 4 We can not find another path, then the answer is 1? 2 is not the answer.

Now we improved algorithm, to flow through the path of construction of the reverse side, like this:

Have the opportunity to go back to the program.

Defined residual amount of hop becomes: capacity - has traffic flows.

The total value of the reverse side of the traffic flow = forward flow, that is to say how many forward flow, reverse flow back to how much you can.

We thus find a path 1-> 3-> 2-> 4.

Built on the reverse side of the path once again, we found no paths to the 4:00, so the answer is two.

 

summary:

To summarize the steps required maximum flow above:

1. Find a path from source to sink in the map (referred to as 'augmenting paths').

2. to increase the minimum residual wide road v. (That is, the flow path flows in the smallest that a)

3. The answer plus v.

Residual all sides of the augmented road minus 4 ,. v, the reverse side of the residual amount plus v.

Repeat step 4 until the top credit not find the way, this method is referred to FF.

 

算法的正确性一会进行证明,我们先看一下这个算法的效率。

首先这个算法应定不会死循环的,应为每次增广都会导致流量增加(并且增加的是整数),而且流量有一个客观存在最大值,所以它必定结束。(不理解不重要啦QAQ)

由于我们并没有指定它走哪一条边,所以优先考虑随便走一条边。

我们考虑一种极限的情况:

现增广1->2->3->4,会出现一条3->2容量为1的边。

再增广1->3->2->4,再增广1->2->3->4....

这浪费大量的时间,如果脸黑的话最多200000次。

然而我们如果先1->2->4,然后1->3->4,走两次就好了,上面的做法是我们不期望的。

我们可以考虑每次增广最短路。

 

EK算法:

EK算法是以上算法的实现:每次寻找最短路进行增广。

时间复杂度$O(m2n)$

首先我们定义几个数组以及变量:

结构体:储存三个变量,nxt,to,dis   [邻接表建边]

flow[ i ] :表示流过 i 点的 v 值,也就是说目前经过到 i 点的路径上的最小的残量。

dis[ i ]:表示 i 点距离源点的距离,S,T表示源点以及汇点。

明确一个观点:

位运算符 ^ :1^1=0  0^1=1  2^1=3  3^1=2.

可以大致明白它的运算效果。

代码推演:

建边的时候,为了方便 ^ 运算符使用,我们可以提前建好反向边,之后一条边,^ 一下就是另一条边了。

首先我们利用bfs处理图的连通性以及所有点与源点的距离,当然,当这条边上的残量已经为0的时候,我们他已经不能经过,我们可以直接不考虑。

在bfs中pre数组是记录每个点最短路的前驱,last数组记录上条边的编号,从而记录出最短路径,然后从汇点进行更新即可。

复制代码
bool bfs(int s,int t)
{
    
    memset(flow,0x7f,sizeof(flow));
    memset(dis,0x7f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    Q.push(s);vis[s]=1;dis[s]=0,pre[t]=-1;
    
    while(!Q.empty())
    {
        int temp=Q.front();
        Q.pop();
        vis[temp]=0;
        for(int i=head[temp];i!=-1;i=edge[i].nxt)
        {
            int v=edge[i].to; 
            
            if(edge[i].flow>0&&dis[v]>dis[temp]+edge[i].dis)
            {
                dis[v]=dis[temp]+edge[i].dis;
                pre[v]=temp;
                last[v]=i;
                flow[v]=min(flow[temp],edge[i].flow);
                if(!vis[v])
                {
                    vis[v]=1;
                    Q.push(v); 
                }
            }
        }
    }
    return pre[t]!=-1;
}
复制代码

 

从汇点向前更新。

复制代码
while(bfs(s,t))
{
     int now=t;
     maxflow+=flow[t];
     mincost+=flow[t]*dis[t];
     while(now!=s)
     {
         edge[last[now]].flow-=flow[t];
         edge[last[now]^1].flow+=flow[t];
         now=pre[now];
     }
 }
复制代码

 

EK算法还能优化么?

在此之前我们先了解一个定理:.

最大流最小割定理

什么是割?

这么来说吧,有个人住在废水收集站站附近,他不想然人们江水流到那,晚上偷偷在某个管道处切了一刀,图成为不联通的两块,从没有水流源点流到汇点。

选出一些管道,切断以后,图不连通,这些管道的集合就叫

这些边的容量之和叫做这个割的容量

任取一个割,其容量大于最大流的流量,why?

从源点到汇点每次都会经过割上的最少一条边。

割掉这条边以后把源点能到达的边放在左边,不能到达的放在右边。

显然源点到会点的流量不会超过从左边走向右边的次数,而这又不会从左边到右边的容量之和。、

直观一点:

当n管道在一起的时候,你一刀全部切断,不在一起的时候你也不至于切n+1刀吧。

最小割的容量等于最大流的流量

这个定理如何证明呢?

■考虑FF算法时,残量网络上没有了增广路。

那么我们假设这时候,从源点经过残量网络能到达的点组成的集合为XX,不能到达的点为YY。显然汇点在YY里,并且残量网络上没有从XX到YY的边。

可以发现以下事实成立:

1.YY到XX的边的流量为0.如果不为0,那么一定存在一条从X到Y的反向边,于是矛盾。

2.XX到YY的边流量等于其容量。只有这样它才不会在残量网络中出现。

■根据第一个条件得知:没有流量从XX到YY后又回到XX。所以当前流量应该等于从XX到YY的边的流量之和,而根据第二个条件他又等于XX到YY的边容量之和。

■而所有从X到Y的边又构成了一个割,其容量等于这些边的容量之和。

★这意味着我们找到一个割和一个流,使得前者的流量等于后者的容量。而根据前边的结论,最大流的流量不超过这个割的容量,所以这个流一定是最大流。

■同样的,最小割的容量也不会小于这个流的流量,所以这个割也一定是最小割。

■而这也正是FF方法的最后局面,由此我们对出结论:

FF是正确的,并且最小割等于最大流

(据说还可以通过线性规划对偶定理证明 ...orz)

 

 EK优--Dinic

 EK时间复杂度太高,虽然大多数情况跑不到上界。

有一个显然的优化:

如果增广一次后发现最短路没有变化,那么可以继续增广,直到源点到汇点的增广路增大,才需要一边bfs。

bfs之后我们去除那些可能在最短路上的边,即dis[终点]=dis[起点]+1的那些边。

显然这些边构成的图中没有环。

 我们只需要延这些边尽可能的增广即可。

 

 实现:

bfs处直接上代码,比较简单。

复制代码
int bfs()
{
    memset(dis,-1,sizeof(dis));
    dis[S]=0;
    Q.push(S);
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop() ;
        for(int i=head[u];i!=-1;i=edge[i].nxt)
        {
            int v=edge[i].to;
            if(dis[v]==-1&&edge[i].w>0)
            {
                dis[v]=dis[u]+1;    //更新 
                Q.push(v); 
            }
        }
    }
    return dis[T]!=-1;    //判断是否联通。 
}
复制代码

 

dfs:

当图联通时进行dfs,目前节点为u,每次经过与u距离最近的点,并且这条边的残量值要大于0,然后往后进行dfs。

我们在dfs是要加一个变量,作为流量控制(后边的流量不能超过前边流量的最小值)。

dfs中变量flow记录这条管道之后的最大流量。

复制代码
bool dfs(int u,int exp)
{
    if(u==T)return exp;    //到达重点,全部接受。 
    int flow=0,tmp=0;    
    for(int i=head[u];i!=-1;i=edge[i].nxt)
    {
        int v=edge[i].to;    //下一个点。 
        if(dis[v]==dis[u]+1&&edge[i].w>0)
        {
            tmp=dfs(v,min(exp,edge[i].w));    //往下进行 
            if(!tmp)continue;
            
            exp-=tmp;    //流量限制-流量,后边有判断。 
            flow+=tmp;
            
            edge[i].w-=tmp;        //路径上的边残量减少 
            edge[i^1].w+=tmp;    //流经的边的反向边残量增加。 
            if(!exp)break;    //判断是否在限制边缘 
        }
    }
    return flow;
}
复制代码

 

重复上边如果图联通(有最短路径),就一直进行增广。

while(bfs())ans+=dfs(S,inf);

时间复杂度: 

Dinic复杂度可以证明是$O(n2m)$

在某些特殊情况下(每个点要么只有一条入边且容量为1,要么仅有一条出边且容量为1)其时间复杂度甚至能做到O(mn−−√)

网络最大流模板
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
#define inf 0x7fffffff
int head[10010],tot;
struct ahah{
    int nxt,to,w;
}edge[100010];
void add(int x,int y,int z)
{
    edge[tot].nxt=head[x];
    edge[tot].to=y;
    edge[tot].w=z;
    head[x]=tot++;
}

int n,m,x,y,z;
int ans,flow;
int dis[10010];
queue <int> Q;
int S,T;

int bfs()
{
    memset(dis,-1,sizeof(dis));
    dis[S]=0;
    Q.push(S);
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop() ;
        for(int i=head[u];i!=-1;i=edge[i].nxt)
        {
            int v=edge[i].to;
            if(dis[v]==-1&&edge[i].w>0)
            {
                dis[v]=dis[u]+1;    //更新 
                Q.push(v); 
            }
        }
    }
    return dis[T]!=-1;    //判断是否联通。 
}

bool dfs(int u,int exp)
{
    if(u==T)return exp;    //到达重点,全部接受。 
    int flow=0,tmp=0;    
    for(int i=head[u];i!=-1;i=edge[i].nxt)
    {
        int v=edge[i].to;    //下一个点。 
        if(dis[v]==dis[u]+1&&edge[i].w>0)
        {
            tmp=dfs(v,min(exp,edge[i].w));    //往下进行 
            if(!tmp)continue;
            
            exp-=tmp;    //流量限制-流量,后边有判断。 
            flow+=tmp;
            
            edge[i].w-=tmp;        //路径上的边残量减少 
            edge[i^1].w+=tmp;    //流经的边的反向边残量增加。 
            if(!exp)break;    //判断是否在限制边缘 
        }
    }
    return flow;
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d%d%d",&n,&m,&S,&T);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);add(y,x,0);    //相邻建边。 
    }
    while(bfs())ans+=dfs(S,inf);
    printf("%d",ans);
 } 

  

当前弧优化:

这优化我也不是太熟悉啦。

当前弧优化的意思就是说每次开始跑邻接表遍历不是从第一条边开始跑而是从上一次点i遍历跑到的点.

我们用cur[i]表示这个点,之后每次建完分层图之后都要进行初始化,且见分层图时不存在当前弧优化.

int deep[N+1];
int q[N+1]= {0},h,t;
int cur[N+1];
bool bfs(int S,int T)
{
    for (int i=0; i<=n; i++) deep[i]=0;    //初始化深度为0
    h=t=1;
    q[1]=S;
    deep[S]=1;
    while (h<=t)
    {
        for (int i=lin[q[h]]; i; i=e[i].next)
            if (!deep[e[i].y]&&e[i].v)       //若未计算过深度且这条边不能是空的
            {
                q[++t]=e[i].y;      //入队一个节点
                deep[q[t]]=deep[q[h]]+1;      //计算深度
            }
        ++h;
    }
    if (deep[T]) return true;
    else return false;
}
int dfs(int start,int T,int minf)
{
    if (start==T) return minf;      //若到了汇点直接返回前面流过来的流量
    int sum=0,flow=0;
    for (int &i=cur[start]; i; i=e[i].next)    //当前弧优化,运用指针在修改i的同时,将cur[start]顺便修改
        if (e[i].v&&deep[start]+1==deep[e[i].y])
        {
            flow=dfs(e[i].y,T,min(minf,e[i].v));      //继续找增广路
            if (!flow) deep[e[i].y]=0;      //去掉已经增广完的点
            sum+=flow;      //统计最大流
            minf-=flow;      //剩余容量
            e[i].v-=flow;
            e[i^1].v+=flow;      //更新剩余容量
            if (!minf) return sum;      //若前面已经流完了,直接返回
        }
    return sum;      //返回最大流量
}
int maxflow(int S,int T)
{
    int sum=0,minf;
    while (1)       //while(1) 控制循环
    {
        if (!bfs(S,T)) return sum;      //bfs求出分层图,顺便判断是否有增广路
        for (int i=1; i<=n; i++)
            cur[i]=lin[i];      //当前弧的初始化
        minf=dfs(S,T,INF);      //dfs求出流量
        if (minf) sum+=minf;      //若流量不为0,加入
        else return sum;      //流量为0,说明没有增广路,返回最大流
    }
}

代码

  

除特别注明外,本站所有文章均为Manjusaka丶梦寒原创,转载请注明来自出处

当前弧优化:

这优化我也不是太熟悉啦。

当前弧优化的意思就是说每次开始跑邻接表遍历不是从第一条边开始跑而是从上一次点i遍历跑到的点.

我们用cur[i]表示这个点,之后每次建完分层图之后都要进行初始化,且见分层图时不存在当前弧优化.

int deep[N+1];
int q[N+1]= {0},h,t;
int cur[N+1];
bool bfs(int S,int T)
{
    for (int i=0; i<=n; i++) deep[i]=0;    //初始化深度为0
    h=t=1;
    q[1]=S;
    deep[S]=1;
    while (h<=t)
    {
        for (int i=lin[q[h]]; i; i=e[i].next)
            if (!deep[e[i].y]&&e[i].v)       //若未计算过深度且这条边不能是空的
            {
                q[++t]=e[i].y;      //入队一个节点
                deep[q[t]]=deep[q[h]]+1;      //计算深度
            }
        ++h;
    }
    if (deep[T]) return true;
    else return false;
}
int dfs(int start,int T,int minf)
{
    if (start==T) return minf;      //若到了汇点直接返回前面流过来的流量
    int sum=0,flow=0;
    for (int &i=cur[start]; i; i=e[i].next)    //当前弧优化,运用指针在修改i的同时,将cur[start]顺便修改
        if (e[i].v&&deep[start]+1==deep[e[i].y])
        {
            flow=dfs(e[i].y,T,min(minf,e[i].v));      //继续找增广路
            if (!flow) deep[e[i].y]=0;      //去掉已经增广完的点
            sum+=flow;      //统计最大流
            minf-=flow;      //剩余容量
            e[i].v-=flow;
            e[i^1].v+=flow;      //更新剩余容量
            if (!minf) return sum;      //若前面已经流完了,直接返回
        }
    return sum;      //返回最大流量
}
int maxflow(int S,int T)
{
    int sum=0,minf;
    while (1)       //while(1) 控制循环
    {
        if (!bfs(S,T)) return sum;      //bfs求出分层图,顺便判断是否有增广路
        for (int i=1; i<=n; i++)
            cur[i]=lin[i];      //当前弧的初始化
        minf=dfs(S,T,INF);      //dfs求出流量
        if (minf) sum+=minf;      //若流量不为0,加入
        else return sum;      //流量为0,说明没有增广路,返回最大流
    }
}

代码

  

Guess you like

Origin www.cnblogs.com/daiyonxin/p/11506026.html