1. 程式人生 > 實用技巧 >網路流求最大流Dinic 當前弧+無用點優化

網路流求最大流Dinic 當前弧+無用點優化

Dinic演算法

  • 在EK演算法(Edmonds-Karp)中,每次BFS後只找到一條增廣路,Dinic演算法每次可以找到多條,從而更加優秀

  • Dinic演算法每次先BFS構造出分層圖,再在分層圖上Dfs找到增廣路,在回溯時更新剩餘容量

  • Dinic演算法時間複雜度\(O(n^2m)\),但實際會跑的很快,一般1e5規模的都可以跑過去

當前弧優化

  • BFS之後每增廣一條路,這條路就一定不會再可以到達匯點,下次就不再遍歷這條邊

  • 體現在程式碼上就是吧head拷貝出來,Dfs的時候實時更新就好了

Code

bool Bfs() {
    memset(dep, 0, n * 4 + 4);
    memcpy(hd, head, n * 4 + 4);//每次把head陣列拷貝一遍
    dep[s] = 1;
    q[l=r=1] = s;
    while (l <= r) {
        int x = q[l++];
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t;
            if (!e[i].d || dep[y]) continue;//如果不能流或已經更新過就不再更新
            dep[y] = dep[x] + 1; q[++r] = y;
            if (y == t) return 1;
        }
    }
    return 0;
}

int Dinic(int x, int lim) {//x是當前點,lim是限制
    if (x == t) return lim;//返回的值才是真正的流量
    int sum = 0;//記錄總流量
    for (int i = hd[x]; i && lim; i = e[i].next) {
        hd[x] = i;//當前弧優化
        int y = e[i].t;
        if (!e[i].d || dep[y] != dep[x] + 1) continue;//只有在不同的層才進行增廣
        int f = Dinic(y, std::min(e[i].d, lim));
        sum += f; lim -= f;
        e[i].d -= f; e[i^1].d += f;
    }
    if (!sum) dep[x] = 0;//無用點優化
    return sum;
}

int main() {
    while (Bfs()) ans += Dinic(s, inf);//直到找不到增廣路就停止
}

例題

[SCOI2007]蜥蜴

題目傳送門

題目描述

  • 在一個r行c列的網格地圖中有一些高度不同的石柱,一些石柱上站著一些蜥蜴,你的任務是讓儘量多的蜥蜴逃到邊界外。 每行每列中相鄰石柱的距離為1,蜥蜴的跳躍距離是d,即蜥蜴可以跳到平面距離不超過d的任何一個石柱上。石柱都不穩定,每次當蜥蜴跳躍時,所離開的石柱高度減1(如果仍然落在地圖內部,則到達的石柱高度不變),如果該石柱原來高度為1,則蜥蜴離開後消失。以後其他蜥蜴不能落腳。任何時刻不能有兩隻蜥蜴在同一個石柱上。

輸入格式

  • 輸入第一行為三個整數r,c,d,即地圖的規模與最大跳躍距離。以下r行為石竹的初始狀態,0表示沒有石柱,1~3表示石柱的初始高度。以下r行為蜥蜴位置,“L”表示蜥蜴,“.”表示沒有蜥蜴。

輸出格式

  • 輸出僅一行,包含一個整數,即無法逃離的蜥蜴總數的最小值。

樣例輸入

5 8 2
00000000
02000000
00321100
02000000
00000000
........
........
..LLLL..
........
........

樣例輸出

1

資料範圍與提示

100%的資料滿足:1<=r, c<=20, 1<=d<=4

Solve

  • 主要考察的是建圖

  • 首先想到給可以在兩個可以到達的點連邊,流量為兩點高度的較小者,然後超級原點向有蜥蜴的點建流量為1的邊,可以跳出邊界的點向超級匯點連流量為inf的邊

  • 然而樣例都過不去

  • 仔細想想這樣只能保證一條路上過的數量不會超過高度,但不能保證這個點不被經過更多次

  • 解決辦法就是將一個點拆成兩個,一個入度點,一個出度點,入度點向出度點建一條流量為高度的邊,其他的點之間建邊的時候都是這個點的出度點向那個點的入度點建邊,這樣就能保證每個點只能被經過高度次

Code

#include <cstdio>
#include <cstring>
#include <algorithm>

const int N = 805;

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

struct Edge {
    int next, t, d;
}e[N*31];
int head[N], edc = 1;

void Add(int x, int y, int z) {
    e[++edc] = (Edge) {head[x], y, z};
    head[x] = edc;
    e[++edc] = (Edge) {head[y], x, 0};
    head[y] = edc;
}

int n, m, s, t, d, h[25][25], hc, dep[N], hd[N], tot, q[N], l, r, ans;
char a[25][25], c[25];

bool Bfs() {
    memset(dep, 0, t * 4 + 4);
    memcpy(hd, head, t * 4 + 4);
    dep[s] = 1;
    q[l=r=1] = s;
    while (l <= r) {
        int x = q[l++];
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t;
            if (!e[i].d || dep[y]) continue;
            dep[y] = dep[x] + 1, q[++r] = y;
            if (y == t) return 1;
        }
    }
    return 0;
}

int Dinic(int x, int lim) {
    if (x == t) return lim;
    int sum = 0;
    for (int i = hd[x]; i && lim; i = e[i].next) {
        hd[x] = i;
        int y = e[i].t;
        if (!e[i].d || dep[y] != dep[x] + 1) continue;
        int f = Dinic(y, std::min(e[i].d, lim));
        lim -= f, sum += f;
        e[i].d -= f, e[i^1].d += f;
    }
    if (!sum) dep[x] = 0;
    return sum;
}

int main() {
    n = read(), m = read(); d = read();
    s = 0, t = n * m * 2 + 1;
    for (int i = 1; i <= n; ++i)
        scanf("%s", a[i] + 1);
    for (int i = 1; i <= n; ++i) {
        scanf("%s", c + 1);
        for (int j = 1; j <= m; ++j) {
            h[i][j] = ++hc;
            if (a[i][j] != '0') Add(h[i][j], h[i][j] + n * m, a[i][j] - '0');
            if (c[j] == 'L') Add(s, h[i][j], 1), tot++;
        }
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if (a[i][j] == '0') continue;
            bool g = 0;
            for (int x = i - d; x <= i + d; ++x) {
                for (int y = j - d; y <= j + d; ++y) {
                    if ((x - i) * (x - i) + (y - j) * (y - j) > d * d) continue;
                    if (x < 1 || x > n || y < 1 || y > m) {
                        if (!g) Add(h[i][j] + n * m, t, N), g = 1;
                        continue;
                    }
                    if (a[x][y] == '0') continue;
                    Add(h[i][j] + n * m, h[x][y], N);
                }
            }
        }
    }
    while (Bfs()) ans += Dinic(s, N);
    printf("%d\n", tot - ans);
    return 0;
}