ICPC Competitive Programming

Max Flow (Dinic's Algorithm)

Dinic's Algorithm สำหรับหา Maximum Flow ใน Network

Dinic’s Algorithm

Dinic’s Algorithm เป็น algorithm สำหรับหา Maximum Flow ใน flow network โดยมี time complexity O(V2E)O(V^2 E) และ O(EV)O(E \sqrt{V}) สำหรับ unit capacity graphs

Flow Network

  • Source ss: จุดเริ่มต้นของ flow
  • Sink tt: จุดสิ้นสุดของ flow
  • Capacity c(u,v)c(u, v): ความจุสูงสุดของ edge (u,v)(u, v)
  • Flow f(u,v)f(u, v): ปริมาณ flow บน edge (u,v)(u, v)

แนวคิดหลัก

  1. BFS สร้าง level graph (เก็บ distance จาก source)
  2. DFS หา blocking flow ใน level graph
  3. ทำซ้ำจนไม่มี augmenting path

Implementation

struct Dinic {
    struct Edge {
        int to, rev;
        long long cap;
    };
    
    int n;
    vector<vector<Edge>> graph;
    vector<int> level, iter;
    
    Dinic(int n) : n(n), graph(n), level(n), iter(n) {}
    
    void addEdge(int from, int to, long long cap) {
        graph[from].push_back({to, (int)graph[to].size(), cap});
        graph[to].push_back({from, (int)graph[from].size() - 1, 0});
    }
    
    // Bidirectional edge (for undirected graphs)
    void addBidirectionalEdge(int from, int to, long long cap) {
        graph[from].push_back({to, (int)graph[to].size(), cap});
        graph[to].push_back({from, (int)graph[from].size() - 1, cap});
    }
    
    bool bfs(int s, int t) {
        fill(level.begin(), level.end(), -1);
        queue<int> q;
        level[s] = 0;
        q.push(s);
        
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            
            for (auto& e : graph[u]) {
                if (e.cap > 0 && level[e.to] < 0) {
                    level[e.to] = level[u] + 1;
                    q.push(e.to);
                }
            }
        }
        
        return level[t] >= 0;
    }
    
    long long dfs(int u, int t, long long f) {
        if (u == t) return f;
        
        for (int& i = iter[u]; i < graph[u].size(); i++) {
            Edge& e = graph[u][i];
            
            if (e.cap > 0 && level[u] < level[e.to]) {
                long long d = dfs(e.to, t, min(f, e.cap));
                
                if (d > 0) {
                    e.cap -= d;
                    graph[e.to][e.rev].cap += d;
                    return d;
                }
            }
        }
        
        return 0;
    }
    
    long long maxFlow(int s, int t) {
        long long flow = 0;
        
        while (bfs(s, t)) {
            fill(iter.begin(), iter.end(), 0);
            
            long long f;
            while ((f = dfs(s, t, LLONG_MAX)) > 0) {
                flow += f;
            }
        }
        
        return flow;
    }
};

ตัวอย่างการใช้งาน

int main() {
    int n, m;
    cin >> n >> m;
    
    Dinic dinic(n);
    
    for (int i = 0; i < m; i++) {
        int u, v;
        long long c;
        cin >> u >> v >> c;
        dinic.addEdge(u, v, c);
    }
    
    int source = 0, sink = n - 1;
    cout << dinic.maxFlow(source, sink) << endl;
    
    return 0;
}

ทฤษฎีที่เกี่ยวข้อง

Max-Flow Min-Cut Theorem

Maximum Flow=Minimum Cut\text{Maximum Flow} = \text{Minimum Cut}

Minimum Cut คือ set of edges ที่มี total capacity น้อยที่สุดที่เมื่อลบออกแล้วไม่มี path จาก ss ไป tt

หา Minimum Cut

vector<pair<int, int>> minCut(int s, int t) {
    maxFlow(s, t);
    
    // BFS from source in residual graph
    vector<bool> visited(n, false);
    queue<int> q;
    q.push(s);
    visited[s] = true;
    
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        
        for (auto& e : graph[u]) {
            if (e.cap > 0 && !visited[e.to]) {
                visited[e.to] = true;
                q.push(e.to);
            }
        }
    }
    
    // Edges crossing the cut
    vector<pair<int, int>> cutEdges;
    for (int u = 0; u < n; u++) {
        if (visited[u]) {
            for (auto& e : graph[u]) {
                if (!visited[e.to] && e.cap == 0) {
                    cutEdges.push_back({u, e.to});
                }
            }
        }
    }
    
    return cutEdges;
}

ประยุกต์ใช้งาน

1. Bipartite Matching

ใช้ max flow โดย:

  • เพิ่ม source ss เชื่อมไปยัง nodes ฝั่งซ้าย (capacity 1)
  • เพิ่ม sink tt เชื่อมจาก nodes ฝั่งขวา (capacity 1)
  • Edges ระหว่าง 2 ฝั่งมี capacity 1
int bipartiteMatching(int leftSize, int rightSize, vector<pair<int,int>>& edges) {
    Dinic dinic(leftSize + rightSize + 2);
    int s = leftSize + rightSize;
    int t = s + 1;
    
    // Connect source to left
    for (int i = 0; i < leftSize; i++) {
        dinic.addEdge(s, i, 1);
    }
    
    // Connect right to sink
    for (int i = 0; i < rightSize; i++) {
        dinic.addEdge(leftSize + i, t, 1);
    }
    
    // Add edges between left and right
    for (auto& [u, v] : edges) {
        dinic.addEdge(u, leftSize + v, 1);
    }
    
    return dinic.maxFlow(s, t);
}

2. Edge-Disjoint Paths

จำนวน edge-disjoint paths จาก ss ไป tt = max flow (capacity 1 ทุก edge)

3. Vertex-Disjoint Paths

แยกแต่ละ vertex vv เป็น vinv_{in} และ voutv_{out} เชื่อมด้วย edge capacity 1

4. Project Selection Problem

ปัญหาเลือก projects ที่มี dependencies และ profits/costs

Complexity

Graph TypeTime Complexity
GeneralO(V2E)O(V^2 E)
Unit capacityO(EV)O(E \sqrt{V})
Unit network (bipartite)O(EV)O(E \sqrt{V})

เปรียบเทียบ Max Flow Algorithms

AlgorithmTime ComplexityBest For
Ford-FulkersonO(EmaxFlow)O(E \cdot maxFlow)Small flows
Edmonds-KarpO(VE2)O(VE^2)General
DinicO(V2E)O(V^2 E)General, bipartite
Push-RelabelO(V2E)O(V^2 E) หรือ O(V3)O(V^3)Dense graphs

Tips

  1. iter array - Dinic’s optimization ไม่ revisit edges ที่ถูก block แล้วใน phase เดียวกัน
  2. Unit capacity - Dinic เร็วมากสำหรับ bipartite matching
  3. Scaling - สำหรับ capacity ใหญ่ อาจใช้ capacity scaling
  4. Multiple sources/sinks - เพิ่ม super source/sink