1. 程式人生 > >讓菜雞講一講網絡流(isap)

讓菜雞講一講網絡流(isap)

edge 道路 程序 數組 http 出現 起點 沒有 time

讓我先講一個故事吧。

一些小精靈要準備從銀月城(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)