首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

POJ 3723 Conscription:Kruskal 求最大權森林轉化最小生成樹

     編輯:關於C++

問題建模

招募 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)。

Kruskal 算法實現

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 天然避免環,生成森林。若圖連通則為樹,否則為森林。

Prim 替代方案

稠密圖(R 接近 N×M)用 Prim + 鄰接矩陣 O(V²) 更快。但本題 R ≤ 50,000 為稀疏圖,Kruskal + 鄰接表優勢明顯。

總結

本題核心是將「招募成本最小化」轉化為「最大權森林」,再通過取負轉為「最小生成樹」。Kruskal 算法配合並查集是稀疏圖 MST 的標準解法。關鍵洞察:二分圖匹配約束(度 ≤1)天然對應森林結構,MST 算法完美契合。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved