招募 N 名女兵、M 名男兵,每人基礎成本 10000。若異性間有關係值 d(0 < d < 10000),且一方已招募,另一方成本減少 d(即 10000-d)。每人最多使用一條關係。求招募所有人最小總成本。
等價轉化:建立二分圖(女兵 0..N-1,男兵 0..M-1 → 偏移 N),邊權為 d。選擇若干邊使所有點被覆蓋(每點度 ≤1),最大化邊權和。即 **最大權匹配/森林**。
總基礎成本 = (N+M) * 10000。每使用一條關係邊 (u,v,d),總成本減少 d。最大化減少量 = 最大化所選邊權和。
因每人最多用一條關係 → 所選邊構成 **森林**(無環)。求最大權森林 = 對邊權取負,求 **最小生成樹**(Kruskal/Prim)。
struct Edge {
int u, v, d; // d 為關係值
bool operator<(const Edge& o) const { return d > o.d; } // 降序,取大邊
};
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
int main() {
int T; scanf("%d", &T);
while (T--) {
int N, M, R; scanf("%d%d%d", &N, &M, &R);
vector edges;
for (int i = 0; i < R; ++i) {
int x, y, d; scanf("%d%d%d", &x, &y, &d);
edges.push_back({x, y + N, d}); // 男兵偏移 N
}
sort(edges.begin(), edges.end()); // d 降序
for (int i = 0; i < N + M; ++i) fa[i] = i;
long long saved = 0;
for (auto& e : edges) {
int fu = find(e.u), fv = find(e.v);
if (fu != fv) {
fa[fu] = fv;
saved += e.d;
}
}
long long ans = 1LL * (N + M) * 10000 - saved;
printf("%lld\n", ans);
}
}
| 步驟 | 說明 |
|---|---|
| 女兵 0..N-1,男兵 N..N+M-1,邊權 = 關係值 d | |
| 算法選擇 | Kruskal:邊排序 + 並查集,O(R log R),R ≤ 50,000 極快 |
| 邊權處理 | 直接按 d 降序(最大權),或取 -d 升序(最小權),邏輯等價 |
| 結果計算 | 基礎成本 - 選中邊權和 = 最小總成本 |
| 多測試用例 | 輸入有空行,scanf 自動跳過空白,無需特殊處理 |
若選中邊形成環,環上某點度 ≥ 2,違反「每人最多用一條關係」。Kruskal 天然避免環,生成森林。若圖連通則為樹,否則為森林。
稠密圖(R 接近 N×M)用 Prim + 鄰接矩陣 O(V²) 更快。但本題 R ≤ 50,000 為稀疏圖,Kruskal + 鄰接表優勢明顯。
本題核心是將「招募成本最小化」轉化為「最大權森林」,再通過取負轉為「最小生成樹」。Kruskal 算法配合並查集是稀疏圖 MST 的標準解法。關鍵洞察:二分圖匹配約束(度 ≤1)天然對應森林結構,MST 算法完美契合。