網絡流

網絡流

定義

  1,網絡流
    用類似水流的定義,可以把一張圖視作一堆管道,從s源源不斷的流水,看最多有多少水可以匯聚到t (此處的水大致可以視作是每秒一定寬度的管道流過多少水之類的。)。
  2,最大流最小割定理。
    顯然如果不把所有可以流的邊都堵死就不能算一個最小割,因爲還有可以到的邊。
    或者說最大流會在一組可以割的邊中選最小的那個割掉,因爲一組邊可以流過的水顯然是受容量最小的邊所限制的。
  3,必須流和可行流
    1,最小割可行邊,
      意思就是最小割中可能出現的邊。
      充要條件:
        1,滿流
        2,在殘餘網絡中找不到x ---> y的路徑
      解釋:
        如果在殘餘網絡中還找得到x--->y的路徑的話,要割掉這條邊就還需要割掉另一條路徑,這顯然是不夠優的。如果是滿流的話顯然不是割掉了這條邊
    2,最小割必須邊
      1,滿流
      2,在殘餘網絡中s 可以到 x, y 可以到 t。
      解釋:
        滿流的原因和上面原因一樣,同時必須邊肯定也是可行邊(顯然可行邊的範圍就要大一些嘛)。如果滿流但s不能到x or y 不能到 t,因爲這樣的話說明在s 到 x(y 到 t)的路上就已經被割掉了,而不是在這裏割的。但是因爲滿流了,所以這是可行的,但是由於割在別的地方,說明不是必須的。
因此s 必須可以到 x, y 必須可以到s才能保證是必須邊,而不是可行邊
        至於實現方法就比較妙了,如果兩個點在一個scc中則表示可以到,因此可行邊需要保證x和y不在一個scc中,而必須邊則還需要額外保證s 和 x 屬於一個scc, y 和 t屬於一個scc。

模板

這裏只放非遞歸版ISAP。雖然看上去比較長,但好寫好調。如果要學習原理的話需要百度?
稍微熟練一點的話,寫起來還是比較快的,十多分鐘吧。

#include<bits/stdc++.h>
using namespace std;
#define R register int
#define LL long long
#define AC 10100
#define ac 201000

const int inf = 1e9;
int n, m, x, s, t, all, addflow, head, tail, ans;
int Head[AC], date[ac], Next[ac], haveflow[ac], tot = 1;
int have[AC], good[AC], q[AC], c[AC], last[AC];

inline int read()
{
    int x = 0;char c = getchar();
    while(c > '9' || c < '0') c = getchar();
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x; 
}

inline void upmin(int &a, int b) {if(b < a) a = b;}
inline void upmax(int &a, int b) {if(b > a) a = b;}

inline void add(int f, int w, int S)
{
    date[++ tot] = w, Next[tot] = Head[f], Head[f] = tot, haveflow[tot] = S;
    date[++ tot] = f, Next[tot] = Head[w], Head[w] = tot, haveflow[tot] = 0;
}

void bfs()
{
    q[++ tail] = t, c[t] = have[1] = 1;
    while(head < tail)
    {
        int x = q[++ head];
        for(R i = Head[x]; i; i = Next[i])
            if(!c[date[i]] && haveflow[i ^ 1])
                have[c[date[i]] = c[x] + 1] ++, q[++ tail] = date[i];
    }
    memcpy(good, Head, sizeof(Head));
}

void aru()
{
    while(x != s)
    {
        haveflow[last[x]] -= addflow;
        haveflow[last[x] ^ 1] += addflow;
        x = date[last[x] ^ 1];
    }
    ans += addflow, addflow = inf;
}

void isap()
{
    bool done;
    x = s, addflow = inf;
    while(c[s] != all + 1)
    {
        if(x == t) aru();
        done = false;
        for(R &i = good[x]; i; i = Next[i])
        {
            int now = date[i];
            if(c[now] == c[x] - 1 && haveflow[i])
            {
                upmin(addflow, haveflow[i]), last[now] = i;
                done = true, x = now; break;
            }
        }   
        if(!done)
        {
            int go = all;
            for(R i = Head[x]; i; i = Next[i])
                if(c[date[i]] && haveflow[i]) upmin(go, c[date[i]]);
            if(!(--have[c[x]])) break;
            have[c[x] = go + 1] ++, good[x] = Head[x];
            if(x != s) x = date[last[x] ^ 1];
        }
    }   
}

void pre()
{
    n = read(), m = read(), s = read(), t = read(), all = n + 3;
    for(R i = 1; i <= m; i ++)
    {
        int x = read(), y = read(), z = read();
        add(x, y, z);
    }
}

int main()
{
//  freopen("in.in", "r", stdin);
    pre();
    bfs();
    isap();
    printf("%d\n", ans);
//  fclose(stdin);
    return 0;
}

常見建模

1,最小路徑覆蓋

  定義:用最少的鏈覆蓋全圖。
  建圖:將每個點分別拆成2個點,一個爲出點,一個爲入點,建立源點和匯點,分別連接s ---> 出點,入點 ---> t,容量爲1,對於圖中的每一條邊,連出點 ---> 入點。ans = 點數 - 最大流
  理解:這是一個二分圖匹配問題,匹配2個點相當於合併2條路徑,所以合併路徑越多越優。

2,最小點覆蓋 && 最大獨立集

定義:
在一個二分圖中,有如下定義:

  • 最小點覆蓋:選出一個點集使得所有邊都被覆蓋,同時使得點權之和最小;
  • 最大獨立集:選出一個點集使得沒有邊2個點都被覆蓋,且點權之和最大。

結論:最小點覆蓋 + 最大獨立集 = 總點集
因爲在最小點覆蓋中,所有邊都被覆蓋了,所以取反之後,每條邊最多隻會選一個端點,因此是一個獨立集。
又由於是最小的點覆蓋,所以取反後的獨立集也是最大的獨立集。

因此我們給這個二分圖增加源匯,然後直接跑最大流。
就是連:

  • s ---> 奇點 : 點權
  • 偶點 ---> t :點權
  • x ---> y : inf

於是最大流就保證了每條邊肯定都被覆蓋了。
同時因爲最大流 = 最小割,所以求出的是最小點覆蓋。

那麼如果要求最大獨立集的話,用總點數減一減就可以了。

3,最大權閉合子圖

  定義:每個點有點權,要求選出一些點,滿足這些點的出邊連向的點也必須被選且點權之和最大。
  建圖:S向正權點連容量爲權值的邊,從負權邊向t連容量爲|權值|的邊,對於原圖上的邊,在圖上連容量爲inf的邊。答案即爲:正權值之和 - 最大流
  理解:可以看做先選了所有的點,然後如果割左邊的邊就是放棄選某些正權點,如果割右邊的邊就是選某些正權點然後承受相應代價。

4,帶上下界網絡流

  1,無源匯帶上下界可行流。
    定義一個數組d[x]表示圖中點x的入度下限和-出度下限和。
    建圖方式爲:
      對於圖中每一條邊,都連流量爲上界-下界的邊,並在加邊的時候統計d[x]。
      對於任意一個點,如果它的d[x] > 0,那麼連s --- > x, 流量爲d[x];
      如果它的d[x] < 0, 那麼連x --- > t, 流量爲-d[x]。
      然後直接從s到t跑網絡流即可,如果滿流即爲合法,否則不合法。
      如何理解?
    因爲下界是必須達到的,因此先把所有的邊都強行達到下界。
    在強行讓所有邊都達到下界後建出的圖不能保證進入流量 = 流出流量,因爲我們建圖的時候,實際上將多餘or少的流量給忽略了,因此我們要再加一些邊表示這些被忽略的流量。
    對於任意一個點,如果d[x] > 0,那麼表示進來的流量有剩餘,還沒有流完,但此時圖中並沒有體現。因此從s給它補充d[x]的流量, 表示x還需要引進額外的d[x]流量。
    反之,如果d[x] < 0,那麼表示進來的流量不夠用,除此之外還需要額外流出去-d[x]的流量,但此時圖中並沒有體現。所以向t連邊表示點x還需要向t輸出-d[x]的流量。
    如果滿流,那麼代表剩餘的流量可以恰好補全不夠的流量,那麼就是可行了。
    同時因爲每條邊都變爲了上界-下界,因此不管怎麼流,都是不會超過上界的,於是就成功的把流量限制在了[下界,上界].
  2,有源匯帶上下界可行流。
    建圖方式與上述相同,只是把原來的源匯連一條t --- > s : inf的邊(現在的超級源匯爲ss, tt)
  3,有源匯帶上下界最大流。
    先做一遍可行流,然後去掉ss和tt,在殘餘網絡上跑最大流,最大流即爲答案。
    因爲有反向邊,所以之前可行流流出的流量會從反向邊流到t,於是基礎流量就會被滿足了,然後就是在這個基礎上增添流量。
    又因爲是最大流,所以跑出來的肯定是最優解。
  4,有源匯帶上下界費用流
    建圖方式和網絡流差不多,原圖上的邊費用不變,新增的邊費用爲0.一開始減掉下界的時候要加上流下界的費用。
    最後和跑出的費用相加,得到最後的費用。

5,切糕模型

問題:給定一個\(n \times m\)的矩陣,給每個位置填上一個\([1, k]\)之間的數,位置\((i, j)\)\(t\)可以得到\(v(i, j, t)\)的代價。方案合法當且僅當每個位置的數與相鄰的四個位置的數相差不超過\(d\).求一個最小總代價。

建圖:先忽略相差不超過\(d\)的限制,考慮這樣建圖:
對於每個位置,拆成\(k\)個點。

  • s ---> (i, j, 1) : v(i, j, 1)
  • (i, j, t - 1) ---> (i, j, t) : v(i, j, t)
  • (i, j, k) ---> t : inf

於是割掉(i, j, t) ---> (i, j, t + 1)的邊就相當於這個位置填\(t\).
再考慮如果限制\(d\)
切糕.png-21.7kB
來分析一下這幅圖:

  • 對於一個左邊的點\(x\),如果右邊取的邊比它小\(2\)以上的話,因爲\(inf\)是割不掉的,因此還會有流量通過這條邊將\(\ge x - 2\)的邊充滿,也就相當於取走了上面的邊。
  • 對於一個左邊的點\(x\),如果右邊取的邊比它大\(2\)以上的話,假設右邊取走了\(y \ (y - x > 2)\),那麼相對於\(y\)而言,就相當於左邊取走了一個比它小\(2\)以上的邊,與上一種情況衝突,所以不會出現。(這兩種情況實際上是站在不同的點上看同一種情況)

因此這個建圖就合法了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章