让菜鸡讲一讲网络流(isap)

Posted iot的Pastebin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了让菜鸡讲一讲网络流(isap)相关的知识,希望对你有一定的参考价值。

让我先讲一个故事吧。

一些小精灵要准备从银月城(S)迁徙到Nibel山(T)。

这两个地方之间的道路构成了一个网络。

每个道路都有它自己的容量,这决定了每天有多少小精灵可以同时从这儿通过。

现在它们想知道,它们迁徙的速度最大是多少只每天。

这就是一道红果果的最大流问题。


在建图时,我们把每条边拆成2条,

它们方向相反,和原来那条边方向相同的边的容量还是原来的容量,

而另一条边的容量就设成0。

当我们要修改剩余容量的时候,

把正方向的边的容量减少,把反方向的边的容量增加,

就可以很方便的修改它了。


一种最朴实的算法是,

每次寻找一条可以从S到T的有可用容量剩余的路径(我们把它叫增广路,虽然我也不知道为什么是这个名字),

不断寻找到没有这种路径为止。

这种算法叫EK,复杂度是\(O(fm)\),f是得到的最大流,m是道路数量。

可见这个算法运行速度很垃圾,特别时遇到这种情况

              /> 次元壁1
 (999/999) __/     |     \__ (999/999)
          /        |        \>
    银月城        (1/1)         Nibel山
          \__      |      __/>
 (999/999)   \     v     /   (999/999)
              \> 次元壁2

寻找的增广路会一直穿过次元壁1和次元壁2来回走动

每次都只找到一条可以通过1只精灵的路

卡成gou bi


因此,我们提出了一个改进的算法:

把每一个点到起点S的距离用BFS求出来,得到一个分层图,

然后在寻找增广路的时候,限制当前点只能往比它距离刚刚好大1的点前进,

在找到一条增广路后,再用BFS更新分层图,继续寻找增广路,肛到你听到为止直到找不到为止。

我们把这个算法叫做Dinic,复杂度是\(O(min(Xuanxue,n^2 \times m))\),n是点数。不要问我Xuanxue是什么鬼

这个算法有一个优化,叫GAP优化,
主要就是把离起点S等于某个距离的点的数量记下来,
如果离起点S的距离等于某值的点全都不见了,
那么这个图就发生了断层,
再也找不到一条增广路出来。
这个时候就阔以提前结束程序哒。
有时候可以为程序提速100倍以上

有些人啊,发现这个狄尼克dinic算法有个大问题,

就是它每一次BFS的意义好像并不大,

毕竟出现改动的道路就那么点。

于是这些人就搞出了一个更快的算法:

isap

复杂度也许是\(O(0.8*(n^2 \times m))\)

和dinic最大的不同就是,它可以在寻找增广路的同时,自己更新分层图。

主要就是在找不到合法的增广路的时候(比如没有满足距离刚刚好大1的点),

就把现在这个点的距离设为离自己最近的出边的点的距离+1。

这里还有个优化叫当前弧优化,不过意义不大。

这是网上某个isap代码

int source;         // 源点
int sink;           // 汇点
int p[max_nodes];   // 可增广路上的上一条弧的编号
int num[max_nodes]; // 和 t 的最短距离等于 i 的节点数量
int cur[max_nodes]; // 当前弧下标
int d[max_nodes];   // 残量网络中节点 i 到汇点 t 的最短距离
bool visited[max_nodes];

// 预处理, 反向 BFS 构造 d 数组
bool bfs()
{
    memset(visited, 0, sizeof(visited));
    queue<int> Q;
    Q.push(sink);
    visited[sink] = 1;
    d[sink] = 0;
    while (!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        for (iterator_t ix = G[u].begin(); ix != G[u].end(); ++ix)
        {
            Edge &e = edges[(*ix)^1];
            if (!visited[e.from] && e.capacity > e.flow)
            {
                visited[e.from] = true;
                d[e.from] = d[u] + 1;
                Q.push(e.from);
            }
        }
    }
    return visited[source];
}

// 增广
int augment()
{
    int u = sink, df = __inf;
    // 从汇点到源点通过 p 追踪增广路径, df 为一路上最小的残量
    while (u != source)
    {
        Edge &e = edges[p[u]];
        df = min(df, e.capacity - e.flow);
        u = edges[p[u]].from;
    }
    u = sink;
    // 从汇点到源点更新流量
    while (u != source)
    {
        edges[p[u]].flow += df;
        edges[p[u]^1].flow -= df;
        u = edges[p[u]].from;
    }
    return df;
}

int max_flow()
{
    int flow = 0;
    bfs();
    memset(num, 0, sizeof(num));
    for (int i = 0; i < num_nodes; i++) num[d[i]]++;
    int u = source;
    memset(cur, 0, sizeof(cur));
    while (d[source] < num_nodes)
    {
        if (u == sink) {
            flow += augment();
            u = source;
        }
        bool advanced = false;
        for (int i = cur[u]; i < G[u].size(); i++)
        { 
            Edge& e = edges[G[u][i]];
            if (e.capacity > e.flow && d[u] == d[e.to] + 1)
            {
                advanced = true;
                p[e.to] = G[u][i];
                cur[u] = i;
                u = e.to;
                break;
            }
        }
        if (!advanced)
        { // retreat
            int m = num_nodes - 1;
            for (iterator_t ix = G[u].begin(); ix != G[u].end(); ++ix)
                if (edges[*ix].capacity > edges[*ix].flow)
                    m = min(m, d[edges[*ix].to]);
            if (--num[d[u]] == 0) break; // gap 优化
            num[d[u] = m+1]++;
            cur[u] = 0;
            if (u != source)
                u = edges[p[u]].from;
        }
    }
    return flow;
}

看起来isap代码量很大。

其实不然,isap可以写的很简单。

这是我的这一题的全部代码

#include<bits/stdc++.h>
using namespace std;
inline int gotcha()
{
    register int a=0,b=1,c=getchar();
    while(!isdigit(c))b^=c=='-',c=getchar();
    while(isdigit(c))a=a*10+c-48,c=getchar();
    return b?a:-a;
}
const int _ = 10002 , __ = 200002;
int to[__],ne[__],v[__],he[_]={0},ecnt=1;
void adde(int a,int b,int c){to[++ecnt]=b,v[ecnt]=c,ne[ecnt]=he[a],he[a]=ecnt;}
int n,m,S,T,dis[_],gap[_];
int dfs(int d,int flw)
{
    if(d==T || flw==0)return flw;
    int i,g,mid=n-1,los=flw;
    for(i=he[d];i;i=ne[i])
        if(v[i]>0)
        {
            if(dis[d]==dis[to[i]]+1)
            {
                g=dfs(to[i],min(los,v[i])),v[i]-=g,v[i^1]+=g,los-=g;
                if(dis[S]>=n)return flw-los;if(!los)break;
            }
            mid=min(mid,dis[to[i]]);
        }
    if(flw==los){if(--gap[dis[d]]==0)dis[S]=n;dis[d]=mid+1,gap[dis[d]]++;}
    return flw-los;
}
int isap(){int ans=0;gap[S]=n;while(dis[S]<n)ans+=dfs(S,1e9);return ans;}
int main()
{
    register int i,j,k,a;
    n=gotcha(),m=gotcha(),S=gotcha(),T=gotcha();
    for(i=1;i<=m;i++)j=gotcha(),k=gotcha(),a=gotcha(),adde(j,k,a),adde(k,j,0);
    printf("%d",isap());
    return 0;
}

我突然有个写伪代码的冲动

要不我就写在这儿吧,还可以增强记忆

大法师的工作
信息:
    现在在哪儿
    现在带领了多少精灵

如果我现在已经到了Nibel山,或者我这里已经没有更多的精灵了,
    那么我就把现在我这儿的精灵数量汇报回去。

现在我要记录这儿离银月城的可能的最短距离,以及我还有多少精灵还滞留在这儿。

我要寻找可以走的道路。
    如果这条路还有精灵的容身之地,
    并且这条路的终点离我们这儿的距离差是1的话,
        我将派出一个大法师信使,
            让他去这条路的终点,
            而她带领精灵的数量是,这条路的剩余容量与我这儿还滞留的精灵数量的最小值,毕竟带多了没有用。
        等她把成功到达终点的精灵数量带回来,
        我就把仍滞留在这儿的精灵的数量记录一下,
        也把这条道路的容量修改一下。
        当我收到了紧急的停止通知(GAP优化),
            我将立刻把成功前往目的地的精灵的数量汇报回去。
        如果我这儿已经没有滞留的精灵了,
            那我就不用寻找道路了。
    
    除此之外,我还要更新这儿离银月城的可能的最短距离。

当我没有把任何精灵送到Nibel山,我会考虑这儿的距离是不是有点问题。
    我会将这里从距离统计的计数君中抹除,
        如果已经没有和这儿距离一样的点,
            那么就散布紧急通知(GAP优化)。
    之后把这里的距离改成可能的最短距离,并且让计数君把这里加入距离统计中。

最后,我将汇报从我这里成功到达Nibel山的精灵的数量。


领主伊萨普的工作
没有信息

我将不断地
    派遣大法师,
        让她带上许多的精灵,
        从银月城出发,
    统计成功到达Nibel山的精灵的数量,
直到收到紧急的停止通知(GAP优化)为止。

以上是关于让菜鸡讲一讲网络流(isap)的主要内容,如果未能解决你的问题,请参考以下文章

让菜鸡讲一讲斜率优化

面试官:请讲一讲IO流核心模块与基本原理是什么?

讲一讲什么叫阻塞非阻塞同步异步

试题库问题(最大流Isap) 网络流

圆桌问题 网络流Isap算法

ISAP网络流算法