Contest5400 - 網路流-1

August_Light發表於2024-08-03

Contest

A 簽到題

B 最大流

Dinic 板子。

Dinic 的整體結構:

ll dinic() {
    ll ans = 0;
    while (bfs()) { // 如果 s 還有能到 t 的增廣路
        fill(cur+1, cur+n+1, 0); // 當前弧最佳化的預處理,暫時不用管
        ans += dfs(s, INF); // 多路增廣
    }
    return ans;
}

BFS 建分層圖:

bool bfs() {
    fill(level+1, level+n+1, 0); // 清空上一次的分層
    queue<int> q;
    level[s] = 1, q.push(s);
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (auto [v, i] : G[u]) {
            if (!level[v] /*沒 BFS 到過*/ && c[i] > f[i] /*還有殘量*/) {
                level[v] = level[u] + 1; // 把 v 扔到 u 的下一層
                q.push(v);
            }
        }
    }
    return level[t] != 0; // 能從 s 到 t
}

DFS 多路增廣:

ll dfs(int u, ll flow) {
    if (u == t)
        return flow;
    ll tmp = flow; // 當前還有多少流能用
    for (int &idx = cur[u]; idx < int(G[u].size()); idx++) { // 當前弧最佳化,之前走過的不要再走
        auto [v, i] = G[u][idx];
        if (level[v] != level[u] + 1) // 在分層圖上不是下一層就別去
            continue;
        ll t = dfs(v, min(tmp, c[i] - f[i])); // min(能用的流, 這條邊的殘量)
        f[i] += t;
        f[i ^ 1] -= t;
        tmp -= t;
        if (tmp == 0) // 沒流了
            break;
    }
    return flow - tmp; // 總共用了多少流
}

完整程式碼:

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = (l); i <= (r); i++)
#define per(i, r, l) for (int i = (r); i >= (l); i--)
using namespace std;
typedef long long ll;

const int MAXN = 200 + 5;
const int MAXM = 5e3 + 5;
const ll INF = 1e12;

int n, m, s, t;
vector<pair<int, int>> G[MAXN];
ll c[MAXM * 2], f[MAXM * 2];
int level[MAXN], cur[MAXN];

bool bfs() {
    fill(level+1, level+n+1, 0);
    queue<int> q;
    level[s] = 1, q.push(s);
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (auto [v, i] : G[u]) {
            if (!level[v] && c[i] > f[i]) {
                level[v] = level[u] + 1;
                q.push(v);
            }
        }
    }
    return level[t] != 0;
}
ll dfs(int u, ll flow) {
    if (u == t)
        return flow;
    ll tmp = flow;
    for (int &idx = cur[u]; idx < int(G[u].size()); idx++) {
        auto [v, i] = G[u][idx];
        if (level[v] != level[u] + 1)
            continue;
        ll t = dfs(v, min(tmp, c[i] - f[i]));
        f[i] += t;
        f[i ^ 1] -= t;
        tmp -= t;
        if (tmp == 0)
            break;
    }
    return flow - tmp;
}
ll dinic() {
    ll ans = 0;
    while (bfs()) {
        fill(cur+1, cur+n+1, 0);
        ans += dfs(s, INF);
    }
    return ans;
}

int main() { ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m >> s >> t;
    rep(i, 1, m) {
        int u, v, w; cin >> u >> v >> w;
        G[u].emplace_back(v, i << 1), c[i << 1] = w;
        G[v].emplace_back(u, i << 1 | 1);
    }
    cout << dinic() << '\n';
    return 0;
}

C 防鼠工程

洛谷原題 P4001 [ICPC-Beijing 2006] 狼抓兔子

簡要題意:給定一張如下的 \(n \times m\) 的網格圖,求其最小割。\(n,m \le 10^3\),保證輸入檔案的大小在 10M 以內。

Sol 1: 最小割 = 最大流

根據最大流最小割定理,最小割等於最大流,Dinic 硬跑最大流即可。

時間複雜度為玄學,雖然點邊都到 \(10^6\) 級別,但是可能是因為圖的特殊性質它 2s 跑過去了。

long long 會被卡空間,開 int 能過。

Sol 2: 平面圖最小割 = 對偶圖最短路

考慮把整張圖割開就是要從左下角的空白區域找一條路一直割到右上角的空白區域,我們建出對偶圖跑最短路 Dijkstra 即可。說不定 SPFA 也能過,畢竟特殊圖

這次時間複雜度不是玄學,是 \(O(n^2 \log n)\)

D 坐座位

簡要題意:給定一張稠密的二分圖,求其最大匹配。設左部點數量為 \(n_1\),右部點數量為 \(n_2\)\(n_1,n_2 \le 200\)

匈牙利演算法板子

建模:令 \(s = n_1 + n_2 + 1, t = n_1 + n_2 + 2\)。原圖的每條邊 \(u \rightarrow v\) 邊權設為 \(1\),給所有左部點 \(u\) 連邊 \(s \rightarrow u\) 邊權為 \(1\),給所有右部點 \(v\) 連邊 \(v \rightarrow t\),跑 Dinic 即可。

值得注意的是,Dinic 跑二分圖的時間複雜度為 \(O(\sqrt n m)\),不是玄學。

坑點:

  • 右部點的編號要加上 \(n_1\)
  • MAXN 要開到 \(n_1 + n_2\),也就是兩倍 \(n_1\)
  • MAXM 要開到 \(m + n_1 + n_2\)(不算反向邊)
  • \(n\) 要開到 \(n_1 + n_2 + 2\),也就是 \(t\)