N 個模塊,每個模塊須分配到核心 A 或 B 運行,成本分別為 Ai、Bi。M 對模塊需數據交換,若分配到同一核心成本為 0,否則額外成本 w。求最小總成本。
N ≤ 20000, M ≤ 200000。
經典二分圖最小割模型:
S 代表核心 B,匯點 T 代表核心 AS → i 容量 Bi(割斷 = 選核心 A,付出 Bi)i → T 容量 Ai(割斷 = 選核心 B,付出 Ai)i ↔ j 雙向邊容量 w(若 i,j 分屬兩側,必割斷一條,付出 w)最小割值 = 最小總成本。最大流 = 最小割(最大流最小割定理)。
class MaxFlow {
struct Edge { int to, cap, rev; };
vector G[MAXN];
int level[MAXN], iter[MAXN];
int N, S, T;
void addEdge(int u, int v, int cap) {
G[u].push_back({v, cap, (int)G[v].size()});
G[v].push_back({u, 0, (int)G[u].size() - 1});
}
void addEdgeUndir(int u, int v, int cap) { // 雙向邊
addEdge(u, v, cap);
addEdge(v, u, cap);
}
bool bfs() {
fill(level, level + N + 1, -1);
queue q; level[S] = 0; q.push(S);
while (!q.empty()) {
int v = q.front(); q.pop();
for (auto &e : G[v]) if (e.cap > 0 && level[e.to] < 0) {
level[e.to] = level[v] + 1; q.push(e.to);
}
}
return level[T] >= 0;
}
int dfs(int v, int f) {
if (v == T) return f;
for (int &i = iter[v]; i < G[v].size(); i++) {
Edge &e = G[v][i];
if (e.cap > 0 && level[v] < level[e.to]) {
int d = dfs(e.to, min(f, e.cap));
if (d > 0) { e.cap -= d; G[e.to][e.rev].cap += d; return d; }
}
}
return 0;
}
int maxFlow(int s, int t) {
S = s; T = t;
int flow = 0, INF = 1e9;
while (bfs()) {
fill(iter, iter + N + 1, 0);
int f; while ((f = dfs(S, INF)) > 0) flow += f;
}
return flow;
}
};
q[MAXN] 確保足夠大,防止 BFS 隊列溢出iter[] 記錄每輪 DFS 遍歷到的邊索引,避免重複遍歷飽和邊(N×2 + M×2)×2 空間(正向+反向)long long 防溢出:邊容量和流量累加可能超 int 範圍#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 20010;
const int MAXM = 200010;
const int MAXV = MAXN + 5;
const int MAXE = (MAXN * 2 + MAXM * 2) * 2 + 100;
const int INF = 0x3f3f3f3f;
int head[MAXV], to[MAXE], cap[MAXE], nxt[MAXE], ecnt = 1;
int level[MAXV], iter[MAXV], q[MAXV];
void addEdge(int u, int v, int c) {
to[++ecnt] = v; cap[ecnt] = c; nxt[ecnt] = head[u]; head[u] = ecnt;
to[++ecnt] = u; cap[ecnt] = 0; nxt[ecnt] = head[v]; head[v] = ecnt;
}
void addEdgeUndir(int u, int v, int c) {
addEdge(u, v, c); addEdge(v, u, c);
}
bool bfs(int S, int T, int N) {
memset(level, -1, (N + 1) * sizeof(int));
int qh = 0, qt = 0; q[qt++] = S; level[S] = 0;
while (qh < qt) {
int u = q[qh++];
for (int e = head[u]; e; e = nxt[e]) {
int v = to[e];
if (cap[e] > 0 && level[v] == -1) {
level[v] = level[u] + 1; q[qt++] = v;
}
}
}
return level[T] != -1;
}
int dfs(int u, int T, int f) {
if (u == T) return f;
for (int &e = iter[u]; e; e = nxt[e]) {
int v = to[e];
if (cap[e] > 0 && level[v] == level[u] + 1) {
int ret = dfs(v, T, min(f, cap[e]));
if (ret) { cap[e] -= ret; cap[e^1] += ret; return ret; }
}
}
return 0;
}
int dinic(int S, int T, int N) {
int flow = 0;
while (bfs(S, T, N)) {
memcpy(iter, head, (N + 1) * sizeof(int));
int f; while ((f = dfs(S, T, INF)) > 0) flow += f;
}
return flow;
}
int main() {
int N, M;
while (scanf("%d%d", &N, &M) == 2) {
ecnt = 1; memset(head, 0, sizeof(head));
int S = 1, T = N + 2;
for (int i = 1; i <= N; i++) {
int A, B; scanf("%d%d", &A, &B);
addEdge(i + 1, T, A); // i -> T: 選A的成本
addEdge(S, i + 1, B); // S -> i: 選B的成本
}
for (int i = 0; i < M; i++) {
int a, b, w; scanf("%d%d%d", &a, &b, &w);
addEdgeUndir(a + 1, b + 1, w); // 雙向邊
}
printf("%d\n", dinic(S, T, N + 2));
}
return 0;
}
輸入: 3 1 1 10 2 10 10 3 2 3 1000 建圖: S->2 (10), 3->T (1) // 模塊1: A=1,B=10 S->3 (10), 4->T (2) // 模塊2: A=2,B=10 S->4 (3), 5->T (10) // 模塊3: A=10,B=3 3<->4 (1000) // 模塊2,3 交換成本1000 最小割:割 S->2(10) + 4->T(2) + 3->4(1000)? 不。 最優:模塊1選A(1),模塊2選A(2),模塊3選B(3),2-3同核無交換成本 總成本 = 1+2+3 = 6? 但樣例輸出 13。 重新檢查:S代表核心B,T代表核心A S->i (Bi) 割斷 = 選A,付出Bi i->T (Ai) 割斷 = 選B,付出Ai 模塊1: A=1,B=10 -> S->2(10), 3->T(1) 模塊2: A=2,B=10 -> S->3(10), 4->T(2) 模塊3: A=10,B=3 -> S->4(3), 5->T(10) 選擇:1選A(割S->2,付10), 2選A(割S->3,付10), 3選B(割5->T,付10) = 30? 錯。 正確理解:最小割 = 選邊使S,T分離,割邊容量和最小 若模塊i在S側(核心B),割掉 i->T (Ai) 若模塊i在T側(核心A),割掉 S->i (Bi) i,j分兩側,割掉 i-j 或 j-i 中至少一條(付w) 模塊1選A(T側): 付Bi=10 模塊2選A(T側): 付Bi=10 模塊3選B(S側): 付Ai=10 2,3同側(T側): 無交換成本 總和=30? 樣例13。 啊!我搞反了! S側 = 核心A,T側 = 核心B S->i (Ai) 割斷 = 選B 付出Ai i->T (Bi) 割斷 = 選A 付出Bi 模塊1選A: 付Bi=10? 不對。 標準建模: S->i 容量 = 選A的代價 i->T 容量 = 選B的代價 i在S側 = 選A,割掉 i->T 付出選B代價 i在T側 = 選B,割掉 S->i 付出選A代價 所以: S->i (Ai=選A成本) -- 割掉表示不選A,即選B,付出Ai i->T (Bi=選B成本) -- 割掉表示不選B,即選A,付出Bi 模塊1: A=1,B=10 -> S->2(1), 3->T(10) 模塊2: A=2,B=10 -> S->3(2), 4->T(10) 模塊3: A=10,B=3 -> S->4(10), 5->T(3) 最優: 模塊1選A(S側): 割 3->T 付 10 模塊2選A(S側): 割 4->T 付 10 模塊3選B(T側): 割 S->4 付 10 總和 30? 還是不對。 看原代碼: S.addedge2(i+1,t,ai); // i->T 容量 Ai S.addedge2(s,i+1,bi); // S->i 容量 Bi addedge2(u,v,w) = addedge(u,v,w) + addedge(v,u,0) 即:S->i (Bi), i->T (Ai) 割集:S側含S,T側含T i在S側: 割 i->T (Ai) i在T側: 割 S->i (Bi) i,j分側: 割 i-j 或 j-i (w) 模塊1選A(S側): 付 Ai=1 模塊2選A(S側): 付 Ai=2 模塊3選B(T側): 付 Bi=3 2,3分側: 付 w=1000 總和 = 1+2+3+1000 = 1006 模塊1選A: 付1 模塊2選B: 付10 模塊3選B: 付3 2,3同側(T側): 0 總和 = 1+10+3 = 14 模塊1選B: 付10 模塊2選A: 付2 模塊3選A: 付10 1,2分側: 但無直接連接 1,3無連接 總和 = 22 模塊1選B: 付10 模塊2選B: 付10 模塊3選A: 付10 總和 = 30 樣例輸出 13。怎麼得出 13? 啊!題目說:Ai 是在核心A運行的成本,Bi 是在核心B運行的成本。 S->i (Bi) 意思是:如果割斷這條邊(即i不在S側,而在T側),付出 Bi,說明i選了核心A?不。 標準最小割建模(Project Selection Problem): 項目選A收益Pa,選B收益Pb,選A和選B有衝突成本C。 源點連項目容量Pa,項目連匯點容量Pb,衝突雙向容量C。 最小割 = 總收益 - 最大收益。 這裡是成本最小化: 總成本 = Σ(選擇成本) + Σ(衝突成本) = Σ(選A? Ai : Bi) + Σ(分離? w : 0) 構造:S->i (Ai), i->T (Bi), i-j (w) 割集:S側 = 選A的點,T側 = 選B的點 i選A(S側): 割 i->T 付出 Bi? 不對。 正確構造: S->i 容量 = Bi (選B成本) i->T 容量 = Ai (選A成本) i在S側(選A): 割 i->T 付出 Ai i在T側(選B): 割 S->i 付出 Bi i,j分側: 割 i-j 付出 w 模塊1選A: 付 A1=1 模塊2選A: 付 A2=2 模塊3選B: 付 B3=3 2,3分側: 付 w=1000 總和=1006 模塊1選A: 付1 模塊2選B: 付10 模塊3選B: 付3 2,3同側: 0 總和=14 模塊1選B: 付10 模塊2選A: 付2 模塊3選B: 付3 1,2無連接 總和=15 模塊1選B: 付10 模塊2選B: 付10 模塊3選A: 付10 總和=30 怎麼是 13? 看原代碼輸出邏輯:cout<5(ai=1), 1->2(bi=10) i=2: 3->5(ai=2), 1->3(bi=10) i=3: 4->5(ai=10), 1->4(bi=3) For(i,m) { S.addedge(a+1,b+1,w); S.addedge(b+1,a+1,w); } a=2,b=3,w=1000: 3->4(1000), 4->3(1000) 圖: S=1, T=5 1->2 (10), 2->5 (1) 1->3 (10), 3->5 (2) 1->4 (3), 4->5 (10) 3<->4 (1000) 最小割: 割集 {1,2,3} | {4,5} : 割 1->4(3) + 3->5(2) + 3->4(1000) = 1005 割集 {1,2} | {3,4,5} : 割 1->3(10) + 2->5(1) + 3->4(1000)? 3在T側,4在T側,3->4不割 = 11 割集 {1,2,4} | {3,5} : 割 1->3(10) + 4->5(10) + 4->3(1000) = 1020 割集 {1,3} | {2,4,5} : 割 1->2(10) + 3->5(2) + 3->4(1000) = 1012 割集 {1} | {2,3,4,5} : 割 1->2(10)+1->3(10)+1->4(3) = 23 割集 {1,2,3,4} | {5} : 割 2->5(1)+3->5(2)+4->5(10) = 13 <-- 這個! 對應選擇: S側 {1,2,3,4} = 模塊1,2,3都在S側 = 都選A? S側含S=1,模塊節點2,3,4都在S側。 割邊:2->5(1), 3->5(2), 4->5(10) 都是 模塊->T 的邊。 容量分別是 ai: 1, 2, 10。 總和 = 13。 但題目說 Ai 是核心A成本,Bi 是核心B成本。 如果都選核心A,成本應該是 1+2+10=13。匹配! 交換成本:都在同一核心,為0。 所以建模是: S->i (Bi) -- 不割 = 選B?還是割 = 選B? i->T (Ai) -- 割 = 付出 Ai = 選A成本 i在S側 = 選A,割 i->T 付出 Ai i在T側 = 選B,割 S->i 付出 Bi 驗證:都選A -> 都在S側 -> 割 2->5(A1=1), 3->5(A2=2), 4->5(A3=10) = 13。正確! 都選B -> 都在T側 -> 割 1->2(B1=10), 1->3(B2=10), 1->4(B3=3) = 23。正確! 原代碼: S.addedge2(i+1,t,ai); // i+1 -> T, 容量 ai S.addedge2(s,i+1,bi); // S -> i+1, 容量 bi 完全匹配。我的理解最後統一了。