Chương 4·Bài 4.9.3

Minimum Cost Flow

Cho một đồ thị có hướng, mỗi cung có một sức chứa và một chi phí cho mỗi đơn vị hàng đi qua, cùng hai đỉnh ss và tt. Hãy chuyển đúng kk đơn vị hàng từ ss tới tt sao cho tổng chi phí nhỏ nhất. Với đồ thị V≤500V \le 500 đỉnh, E≤1000E \le 1000 cung và k≤100k \le 100, bài này giải được bằng cách tăng luồng kk lần, mỗi lần theo đường đi rẻ nhất trong đồ thị dư.

Đây là bài toán luồng chi phí nhỏ nhất (min-cost flow). Ở bài luồng cực đại, mọi đường tăng luồng đều như nhau; ở đây ta chọn đường rẻ nhất, và cung ngược mang chi phí âm để việc rút lại hàng cũng được tính tiền đúng. Bài này dạy cách đó với thuật toán tìm đường ngắn nhất chịu được chi phí âm, rồi cách dùng thế đỉnh (potential) để chạy Dijkstra nhanh hơn, và cuối cùng là các bài quy về luồng chi phí: phân công, tìm kk đường không chung cung.

ITăng luồng theo đường đi rẻ nhất

Một mạng có chi phí gồm các cung u→vu \to v, mỗi cung có sức chứa c(u,v)c(u, v) và chi phí a(u,v)a(u, v) cho mỗi đơn vị luồng; luồng f(u,v)f(u, v) trên cung đó tốn f(u,v)⋅a(u,v)f(u, v) \cdot a(u, v), và chi phí của luồng là tổng trên mọi cung. Ta muốn một luồng giá trị đúng kk có chi phí nhỏ nhất. Nếu mạng không có luồng giá trị kk thì không có đáp án.

Cách vét cạn là thử mọi cách phân kk đơn vị lên các cung, quá nhiều khi đồ thị lớn. Ta có thể bắt đầu từ cách của bài luồng cực đại: lần lượt tăng luồng theo các đường tăng luồng (bài 4.9.1). Câu hỏi là chọn đường nào. Nếu chọn đường tùy ý thì luồng có thể đắt hơn mức cần thiết; nếu mỗi lần chọn đường rẻ nhất trong đồ thị dư thì kết quả tối ưu. Cung ngược v→uv \to u của cung u→vu \to v có chi phí −a(u,v)-a(u, v), vì rút lại một đơn vị hàng cũng lấy lại tiền đã tốn.

Hỏi nhanh

Cung a→ba \to b có sức chứa 2 và chi phí 5 cho mỗi đơn vị. Sau khi đẩy 1 đơn vị qua nó, trong đồ thị dư có cung ngược b→ab \to a với sức chứa và chi phí là bao nhiêu?

Ví dụ nhỏ: mạng có bốn đỉnh s,a,b,ts, a, b, t và năm cung, mọi sức chứa đều là 1. Các cung s→as \to a, a→ba \to b và b→tb \to t có chi phí 1; cung s→bs \to b và a→ta \to t có chi phí 4. Ta cần k=2k = 2 đơn vị. Đường rẻ nhất là s→a→b→ts \to a \to b \to t, chi phí 3. Sau khi đẩy một đơn vị theo đó, ba cung của đường này đầy, đồ thị dư chỉ còn ba cung như hình, và đường tăng luồng duy nhất là s→b→a→ts \to b \to a \to t với chi phí 4+(−1)+4=74 + (-1) + 4 = 7. Tổng chi phí là 3+7=103 + 7 = 10, đúng bằng cách đi riêng s→a→ts \to a \to t và s→b→ts \to b \to t, tốn 5+55 + 5.

Đồ thị dư sau khi đẩy 1 đơn vị theo s, a, b, t; nhãn là chi phí, cung ngược từ b về a có chi phí -1.4-14sabt
Đồ thị dư sau khi đẩy 1 đơn vị theo s, a, b, t; nhãn là chi phí, cung ngược từ b về a có chi phí -1.

Vì có cung chi phí âm nên không dùng Dijkstra thẳng được. Ta dùng SPFA, tức thuật toán Bellman-Ford có hàng đợi: một đỉnh được đưa vào hàng đợi mỗi khi khoảng cách tới nó giảm, và khi lấy ra, ta thử cải thiện khoảng cách tới các đỉnh kề. SPFA đúng với chi phí âm miễn là đồ thị không có chu trình âm; đồ thị dư của ta không có (xem ô Chứng minh).

Chứng minh

Đây là các ý chính, không phải chứng minh đầy đủ.

Luồng giá trị vv rẻ nhất khi và chỉ khi đồ thị dư không có chu trình âm. Nếu có chu trình âm, đẩy một đơn vị quanh chu trình đó giữ nguyên giá trị luồng và giảm chi phí. Ngược lại, hiệu của hai luồng cùng giá trị là một dòng tuần hoàn, phân tách được thành các chu trình trong đồ thị dư của luồng thứ nhất; nếu mọi chu trình không âm thì luồng thứ hai không rẻ hơn.

Tăng theo đường rẻ nhất giữ tính chất này. Gọi d(v)d(v) là khoảng cách ngắn nhất từ ss tới vv trong đồ thị dư hiện tại, tính cho các đỉnh tới được. Với mọi cung u→vu \to v trong đồ thị dư mà uu tới được, d(v)≤d(u)+chi phıˊ(u→v)d(v) \le d(u) + \text{chi phí}(u \to v), và dấu bằng xảy ra trên đường vừa chọn. Sau khi đẩy, các cung mới là cung ngược của các cung trên đường, đều cũng thỏa dấu bằng. Vậy mọi cung giữa các đỉnh tới được vẫn có d(u)+chi phıˊ−d(v)≥0d(u) + \text{chi phí} - d(v) \ge 0; cộng dọc một chu trình thì các dd triệt tiêu, nên tổng chi phí chu trình không âm. Cung đi từ đỉnh tới được sang đỉnh không tới được không tồn tại, nên chu trình đi qua một đỉnh tới được chỉ nằm trong các đỉnh tới được; còn chu trình gồm toàn đỉnh không tới được thì không bị thay đổi.

Mỗi lần đẩy ta lấy lượng bằng sức chứa nhỏ nhất trên đường, nhưng không vượt quá số đơn vị còn thiếu. Chi phí tăng thêm bằng lượng đẩy nhân chi phí của đường.

Hỏi nhanh

Một bạn tăng luồng theo đường rẻ nhất nhưng cộng chi phí bằng ans += d[t]; thay vì lượng đẩy nhân d[t]. Mạng chỉ có một cung s→ts \to t với sức chứa 5 và chi phí 3, và cần k=4k = 4 đơn vị. Chương trình in bao nhiêu?

Ví dụChuyển Kiện Tiết Kiệm

Đề. Có n≤500n \le 500 thành phố và m≤1000m \le 1000 tuyến vận chuyển có hướng; tuyến a→ba \to b chở tối đa rr kiện và mỗi kiện qua tuyến đó tốn cc (1≤r,c≤10001 \le r, c \le 1000). Cần chuyển đúng k≤100k \le 100 kiện từ thành phố 11 tới thành phố nn. In tổng chi phí nhỏ nhất, hoặc −1-1 nếu không đủ sức chứa.

Lời giải. Mỗi tuyến là một cung với sức chứa rr và chi phí cc, và ta cần luồng giá trị kk nhỏ nhất chi phí. Ta tăng luồng cho tới khi đủ kk đơn vị hoặc không còn đường; nếu chưa đủ thì in −1-1. Mảng pe[v] ghi cung dẫn vào vv trên đường rẻ nhất, từ đó đi ngược từ tt về ss.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 505, M = 2005, INF = 1e18;
ll n, m, k, ec, to[M], cap[M], cost[M], d[N], pe[N], inq[N];
vector<ll> g[N];

void addEdge(ll u, ll v, ll c, ll w) {
    to[ec] = v, cap[ec] = c, cost[ec] = w, g[u].push_back(ec++);
    to[ec] = u, cap[ec] = 0, cost[ec] = -w, g[v].push_back(ec++);
}

bool spfa(ll s, ll t) {
    for (ll i = 1; i <= n; i++) d[i] = INF, inq[i] = 0;
    queue<ll> q;
    d[s] = 0;
    q.push(s);
    while (!q.empty()) {
        ll u = q.front();
        q.pop();
        inq[u] = 0;
        for (ll e : g[u])
            if (cap[e] > 0 && d[u] + cost[e] < d[to[e]]) {
                d[to[e]] = d[u] + cost[e];
                pe[to[e]] = e;
                if (!inq[to[e]]) inq[to[e]] = 1, q.push(to[e]);
            }
    }
    return d[t] < INF;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m >> k;
    for (ll i = 1; i <= m; i++) {
        ll a, b, r, c;
        cin >> a >> b >> r >> c;
        addEdge(a, b, r, c);
    }
    ll flow = 0, ans = 0;
    while (flow < k && spfa(1, n)) {
        ll f = k - flow;
        for (ll v = n; v != 1; v = to[pe[v] ^ 1]) f = min(f, cap[pe[v]]);
        for (ll v = n; v != 1; v = to[pe[v] ^ 1]) {
            cap[pe[v]] -= f;
            cap[pe[v] ^ 1] += f;
        }
        flow += f;
        ans += f * d[n];
    }
    cout << (flow < k ? -1 : ans) << '\n';
}
4 5 3
1 2 5 100
1 3 10 50
1 4 7 500
2 4 8 350
3 4 2 100

Độ phức tạp: mỗi lần tăng luồng đẩy ít nhất 1 đơn vị nên có không quá kk lần, mỗi lần SPFA tốn O(VE)O(VE) trong trường hợp xấu nhất; với k=100k = 100, V=500V = 500, E=2000E = 2000 (tính cả cung ngược) cận này là 10810^8, và thực tế SPFA nhanh hơn nhiều.

IIThế đỉnh và Dijkstra

SPFA có thể chậm khi đồ thị lớn. Ta muốn dùng Dijkstra, nhưng nó cần mọi cung không âm. Thế đỉnh (potential) là một số h(v)h(v) gán cho mỗi đỉnh, dùng để đổi chi phí của cung u→vu \to v thành chi phí rút gọn

a′(u,v)=a(u,v)+h(u)−h(v).a'(u, v) = a(u, v) + h(u) - h(v).

Mọi đường đi từ ss tới tt có chi phí rút gọn bằng chi phí thật cộng h(s)−h(t)h(s) - h(t), vì các hh ở giữa triệt tiêu, nên đường nào ngắn nhất theo chi phí rút gọn cũng ngắn nhất theo chi phí thật. Nếu chọn hh làm cho mọi cung của đồ thị dư có a′≥0a' \ge 0 thì Dijkstra dùng được.

Cách chọn hh là dùng chính khoảng cách đã tính. Sau mỗi lần Dijkstra, ta có khoảng cách rút gọn d′(v)d'(v) từ ss, và cập nhật h(v)←h(v)+d′(v)h(v) \gets h(v) + d'(v). Khi đó chi phí thật của đường ngắn nhất tới tt chính là h(t)h(t), vì h(s)=0h(s) = 0 luôn đúng. Các cung nằm trên đường vừa đẩy có chi phí rút gọn mới bằng 00, nên cung ngược của chúng cũng 00, và các cung khác vẫn không âm theo ý của ô Chứng minh ở mục trên. Đỉnh không tới được thì giữ nguyên thế: về sau nó vẫn không tới được.

Còn thế ban đầu: nếu mọi cung của mạng có chi phí không âm thì chọn h=0h = 0. Nếu có cung chi phí âm, ta chạy Bellman-Ford một lần từ ss để có hh ban đầu, sau đó chỉ dùng Dijkstra.

Ví dụPhân Công Tối Ưu

Đề. Có n≤200n \le 200 nhân viên và nn công việc; nhân viên ii nhận công việc jj tốn ci[j]c_i[j] (1≤ci[j]≤10001 \le c_i[j] \le 1000). Mỗi nhân viên nhận đúng một công việc và mỗi công việc giao cho đúng một nhân viên. In tổng chi phí nhỏ nhất và một phép gán tối ưu (mỗi dòng gồm nhân viên và công việc được giao).

Lời giải. Dựng mạng S→S \to nhân viên →\to công việc →T\to T. Mỗi cung S→iS \to i và mỗi cung công việc →T\to T có sức chứa 1 và chi phí 0; cung nhân viên i→i \to công việc jj có sức chứa 1 và chi phí ci[j]c_i[j]. Một luồng giá trị nn phải đẩy đủ nn đơn vị qua nn cung ra khỏi SS, mỗi nhân viên đúng một đơn vị, mỗi công việc đúng một đơn vị, nên nó chính là một phép gán, và chi phí của luồng là tổng chi phí phép gán. Luồng chi phí nhỏ nhất cho phép gán tối ưu. Các cung nhân viên →\to công việc đã đầy (sức chứa dư bằng 0) cho biết ai nhận việc nào. Mạng có 2n+22n + 2 đỉnh và n2+2nn^2 + 2n cung, nên Dijkstra với thế đỉnh thích hợp; Hungary (thuật toán chuyên cho bài phân công, O(n3)O(n^3)) là một lựa chọn khác.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 405, M = 81005, INF = 1e18;
ll n, V, ec, to[M], cap[M], cost[M], h[N], d[N], pe[N];
vector<ll> g[N];

void addEdge(ll u, ll v, ll c, ll w) {
    to[ec] = v, cap[ec] = c, cost[ec] = w, g[u].push_back(ec++);
    to[ec] = u, cap[ec] = 0, cost[ec] = -w, g[v].push_back(ec++);
}

bool dijkstra(ll s, ll t) {
    for (ll i = 0; i < V; i++) d[i] = INF;
    priority_queue<pair<ll, ll>, vector<pair<ll, ll> >, greater<pair<ll, ll> > > q;
    d[s] = 0;
    q.push(make_pair(0, s));
    while (!q.empty()) {
        ll du = q.top().first, u = q.top().second;
        q.pop();
        if (du > d[u]) continue;
        for (ll e : g[u]) {
            ll v = to[e], nd = du + cost[e] + h[u] - h[v];
            if (cap[e] > 0 && nd < d[v]) {
                d[v] = nd;
                pe[v] = e;
                q.push(make_pair(nd, v));
            }
        }
    }
    if (d[t] >= INF) return 0;
    for (ll i = 0; i < V; i++)
        if (d[i] < INF) h[i] += d[i];
    return 1;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    ll s = 0, t = 2 * n + 1;
    V = 2 * n + 2;
    for (ll i = 1; i <= n; i++) {
        addEdge(s, i, 1, 0);
        addEdge(n + i, t, 1, 0);
        for (ll j = 1; j <= n; j++) {
            ll c;
            cin >> c;
            addEdge(i, n + j, 1, c);
        }
    }
    ll ans = 0;
    while (dijkstra(s, t)) {
        ll f = INF;
        for (ll v = t; v != s; v = to[pe[v] ^ 1]) f = min(f, cap[pe[v]]);
        for (ll v = t; v != s; v = to[pe[v] ^ 1]) {
            cap[pe[v]] -= f;
            cap[pe[v] ^ 1] += f;
        }
        ans += f * h[t];
    }
    cout << ans << '\n';
    for (ll i = 1; i <= n; i++)
        for (ll e : g[i])
            if (e % 2 == 0 && to[e] > n && to[e] <= 2 * n && cap[e] == 0) cout << i << ' ' << to[e] - n << '\n';
}
4
17 8 16 9
7 15 12 19
6 9 10 11
14 7 13 10

Độ phức tạp: có nn lần tăng luồng, mỗi lần Dijkstra tốn O(Elog⁡V)O(E \log V) với E≈n2E \approx n^2; với n=200n = 200 cần khoảng 200⋅4⋅104⋅9≈7⋅107200 \cdot 4 \cdot 10^4 \cdot 9 \approx 7 \cdot 10^7 phép tính.

IIICác bài quy về luồng chi phí nhỏ nhất

Nhiều bài trông không liên quan cũng đưa được về luồng chi phí nhỏ nhất: phân công, vận chuyển từ nhiều nguồn tới nhiều đích (thêm một nguồn chung nối tới các nguồn thật), và chọn đối tượng với giới hạn số lần dùng (sức chứa của cung là giới hạn đó). Điều cần làm là xác định ba thứ: đỉnh là gì, cung nào có sức chứa bao nhiêu, và đại lượng cần tối thiểu là chi phí của cung nào.

Ví dụĐội Giao Hàng Không Chung Cung

Đề. Có n≤500n \le 500 điểm và m≤1000m \le 1000 tuyến một chiều. Phải chọn đúng kk lộ trình từ điểm 11 tới điểm nn sao cho mỗi tuyến được dùng trong nhiều nhất một lộ trình, và tổng số tuyến đã đi là nhỏ nhất. In −1-1 nếu không chọn được; nếu được, in tổng số tuyến, rồi với mỗi lộ trình in số đỉnh trên đường và dãy đỉnh.

Lời giải. Gán cho mỗi tuyến sức chứa 1 (mỗi tuyến dùng tối đa một lần) và chi phí 1 (mỗi tuyến đã đi đếm là một). Khi đó kk lộ trình không chung cung cho một luồng nguyên giá trị kk, và chi phí luồng chính là tổng số tuyến. Ngược lại, từ một luồng nguyên giá trị kk ta lấy lại được kk lộ trình không chung cung bằng cách đi từ 11 theo các cung đang có luồng, mỗi lần lấy một cung chưa dùng, cho tới khi tới nn; luồng chi phí nhỏ nhất không có chu trình vì chi phí dương, nên cách đi này không lặp. Không đủ kk đơn vị thì in −1-1. Ta in các lộ trình theo thứ tự từ điển.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 505, M = 2005, INF = 1e18;
ll n, m, k, ec, to[M], cap[M], cost[M], d[N], pe[N], inq[N], used[M];
vector<ll> g[N];
vector<vector<ll> > ps;

void addEdge(ll u, ll v, ll c, ll w) {
    to[ec] = v, cap[ec] = c, cost[ec] = w, g[u].push_back(ec++);
    to[ec] = u, cap[ec] = 0, cost[ec] = -w, g[v].push_back(ec++);
}

bool spfa(ll s, ll t) {
    for (ll i = 1; i <= n; i++) d[i] = INF, inq[i] = 0;
    queue<ll> q;
    d[s] = 0;
    q.push(s);
    while (!q.empty()) {
        ll u = q.front();
        q.pop();
        inq[u] = 0;
        for (ll e : g[u])
            if (cap[e] > 0 && d[u] + cost[e] < d[to[e]]) {
                d[to[e]] = d[u] + cost[e];
                pe[to[e]] = e;
                if (!inq[to[e]]) inq[to[e]] = 1, q.push(to[e]);
            }
    }
    return d[t] < INF;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m >> k;
    for (ll i = 1; i <= m; i++) {
        ll a, b;
        cin >> a >> b;
        addEdge(a, b, 1, 1);
    }
    ll flow = 0, ans = 0;
    while (flow < k && spfa(1, n)) {
        ll f = k - flow;
        for (ll v = n; v != 1; v = to[pe[v] ^ 1]) f = min(f, cap[pe[v]]);
        for (ll v = n; v != 1; v = to[pe[v] ^ 1]) {
            cap[pe[v]] -= f;
            cap[pe[v] ^ 1] += f;
        }
        flow += f;
        ans += f * d[n];
    }
    if (flow < k) {
        cout << -1 << '\n';
        return 0;
    }
    for (ll i = 1; i <= k; i++) {
        vector<ll> p;
        ll v = 1;
        p.push_back(v);
        while (v != n) {
            for (ll e : g[v])
                if (e % 2 == 0 && cap[e] == 0 && !used[e]) {
                    used[e] = 1;
                    v = to[e];
                    break;
                }
            p.push_back(v);
        }
        ps.push_back(p);
    }
    sort(ps.begin(), ps.end());
    cout << ans << '\n';
    for (ll i = 0; i < k; i++) {
        cout << ps[i].size() << '\n';
        for (ll v : ps[i]) cout << v << ' ';
        cout << '\n';
    }
}
5 6 2
1 2
2 5
1 3
3 4
4 5
2 3

Độ phức tạp: SPFA O(VE)O(VE) mỗi lần và không quá kk lần tăng luồng, với k=499k = 499, V=500V = 500, E=2000E = 2000 cận trên là 5⋅1085 \cdot 10^8 nhưng thực tế chạy nhanh hơn nhiều; phần tách đường tốn O(k⋅E)O(k \cdot E) nhiều nhất.

Bài nâng cao

Xem 1 bài nâng cao

Ví dụDãy Gần Hoán Vị

Đề. Cần chọn dãy a1,…,ana_1, \dots, a_n với n≤50n \le 50, mỗi phần tử thuộc [1,n][1, n], thỏa q≤100q \le 100 ràng buộc: loại 1 (l,r,v)(l, r, v) đòi ax≥va_x \ge v với mọi x∈[l,r]x \in [l, r], loại 2 đòi ax≤va_x \le v. Gọi cnt(y)\mathrm{cnt}(y) là số lần giá trị yy xuất hiện; tối thiểu hóa ∑ycnt(y)2\sum_y \mathrm{cnt}(y)^2, hoặc in −1-1 nếu không có dãy hợp lệ.

Lời giải. Từ các ràng buộc, mỗi vị trí ii có một khoảng giá trị hợp lệ [loi,hii][lo_i, hi_i]. Ta xếp mỗi vị trí vào một giá trị trong khoảng của nó, và cần tối thiểu tổng bình phương số vị trí ở mỗi giá trị. Chi phí cnt2\mathrm{cnt}^2 là chi phí lồi, tăng nhanh dần, và có thể cài bằng luồng chi phí nhỏ nhất: với mỗi giá trị yy, thay một cung bằng nn cung sức chứa 1 tới TT với chi phí 1,3,5,…,2n−11, 3, 5, \dots, 2n - 1. Dùng jj cung rẻ nhất thì tổng chi phí là 1+3+⋯+(2j−1)=j21 + 3 + \dots + (2j - 1) = j^2, và luồng chi phí nhỏ nhất luôn dùng các cung rẻ trước. Mạng là S→S \to vị trí (sức chứa 1, chi phí 0), vị trí →\to giá trị trong khoảng hợp lệ (sức chứa 1, chi phí 0), giá trị →T\to T (các cung chi phí lẻ). Nếu luồng không đủ nn đơn vị thì in −1-1.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 105, M = 12005, INF = 1e18;
ll n, q, V, ec, lo[N], hi[N], to[M], cap[M], cost[M], d[N], pe[N], inq[N];
vector<ll> g[N];

void addEdge(ll u, ll v, ll c, ll w) {
    to[ec] = v, cap[ec] = c, cost[ec] = w, g[u].push_back(ec++);
    to[ec] = u, cap[ec] = 0, cost[ec] = -w, g[v].push_back(ec++);
}

bool spfa(ll s, ll t) {
    for (ll i = 0; i < V; i++) d[i] = INF, inq[i] = 0;
    queue<ll> qu;
    d[s] = 0;
    qu.push(s);
    while (!qu.empty()) {
        ll u = qu.front();
        qu.pop();
        inq[u] = 0;
        for (ll e : g[u])
            if (cap[e] > 0 && d[u] + cost[e] < d[to[e]]) {
                d[to[e]] = d[u] + cost[e];
                pe[to[e]] = e;
                if (!inq[to[e]]) inq[to[e]] = 1, qu.push(to[e]);
            }
    }
    return d[t] < INF;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> q;
    for (ll i = 1; i <= n; i++) lo[i] = 1, hi[i] = n;
    for (ll i = 1; i <= q; i++) {
        ll t, l, r, v;
        cin >> t >> l >> r >> v;
        for (ll x = l; x <= r; x++) {
            if (t == 1) lo[x] = max(lo[x], v);
            else hi[x] = min(hi[x], v);
        }
    }
    ll s = 0, t = 2 * n + 1;
    V = 2 * n + 2;
    for (ll i = 1; i <= n; i++) {
        addEdge(s, i, 1, 0);
        for (ll y = lo[i]; y <= hi[i]; y++) addEdge(i, n + y, 1, 0);
        for (ll j = 1; j <= n; j++) addEdge(n + i, t, 1, 2 * j - 1);
    }
    ll flow = 0, ans = 0;
    while (spfa(s, t)) {
        ll f = INF;
        for (ll v = t; v != s; v = to[pe[v] ^ 1]) f = min(f, cap[pe[v]]);
        for (ll v = t; v != s; v = to[pe[v] ^ 1]) {
            cap[pe[v]] -= f;
            cap[pe[v] ^ 1] += f;
        }
        flow += f;
        ans += f * d[t];
    }
    cout << (flow < n ? -1 : ans) << '\n';
}
4 2
1 1 2 2
2 3 4 3

Độ phức tạp: mạng có 2n+22n + 2 đỉnh và O(n2)O(n^2) cung, nn lần tăng luồng mỗi lần SPFA O(VE)O(VE); với n=50n = 50 cần khoảng 50⋅100⋅5000=2,5⋅10750 \cdot 100 \cdot 5000 = 2{,}5 \cdot 10^7 phép tính.

Khi đi thi

  • Có chi phí âm ban đầu: chạy Bellman-Ford một lần để có thế đỉnh, rồi dùng Dijkstra cho mọi lần sau.
  • Luồng nhỏ (tổng luồng vài trăm tới vài nghìn) thì SPFA đủ nhanh; luồng lớn hay đồ thị dày thì cần Dijkstra với thế đỉnh.
  • Chi phí lồi theo lượng luồng qua một đối tượng: tách thành nhiều cung sức chứa 1 với chi phí tăng dần.

Tóm tắt

  • Luồng chi phí nhỏ nhất cho giá trị kk có được bằng cách tăng luồng kk lần, mỗi lần theo đường rẻ nhất trong đồ thị dư; cung ngược có chi phí đối của cung xuôi.
  • Đồ thị dư của một luồng rẻ nhất không có chu trình âm, nên SPFA tìm đường rẻ nhất đúng dù có cung chi phí âm.
  • Thế đỉnh hh làm chi phí rút gọn a+h(u)−h(v)a + h(u) - h(v) không âm; sau mỗi lần Dijkstra, cộng khoảng cách vào hh, và chi phí thật của đường là h(t)h(t).
  • Phân công là luồng giá trị nn trên mạng ghép cặp có chi phí; kk đường không chung cung là luồng giá trị kk với sức chứa và chi phí đều 1.
  • Chi phí lồi tách thành các cung sức chứa 1 với chi phí tăng dần.

Bài tập

Đọc thêm