最小生成树一般有两种方法,即Kruskal(克鲁斯卡尔)算法和Prim(普里姆)算法。前者以边来生成,后者以顶点来生成。这里介绍Kruskal算法。

具体思路是,将所有m条边按照权重w从小到大排序,然后遍历其中的每一条边,判断该边的两个顶点是否联通,若没有联通,则选取该边,并将两个顶点联通。

这里一个比较关键步骤就是判断两个点是否联通,可以使用并查集。若两个点联通,则这两个点在同一个集合中,否则不在一个集合中。

下面是代码。

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

class UnionFindSet {
  public:
    UnionFindSet(int n) : ufs(n + 1, -1) {
    }

    int findRoot(int x) {
        while (ufs[x] > 0) {
            x = ufs[x];
        }
        return x;
    }

    void merge(int x1, int x2) {
        int root1 = findRoot(x1);
        int root2 = findRoot(x2);
        if (root1 == root2) return;
        if (root1 > root2) swap(root1, root2);
        ufs[root1] += ufs[root2];
        ufs[root2] = root1;
    }

    bool inSet(int x1, int x2) {
        return findRoot(x1) == findRoot(x2);
    }

  private:
    vector<int> ufs;
};

struct edge {
    int id, u, v, w;
};

int main() {
    int n, m;
    cin >> n >> m;
    UnionFindSet ufs(n);
    vector<edge> ed(m);
    vector<int> edAns;
    edAns.reserve(n - 1);
    for (int i = 0; i < m; ++i) {
        cin >> ed[i].u >> ed[i].v >> ed[i].w;
        ed[i].id = i + 1;
    }
    sort(ed.begin(), ed.end(), [](edge e1, edge e2) {
        return e1.w < e2.w;
    });
    long long sum = 0;
    for (int i = 0; i < m; ++i) {
        if (ufs.inSet(ed[i].u, ed[i].v)) continue;
        ufs.merge(ed[i].u, ed[i].v);
        edAns.push_back(ed[i].id);
        sum += ed[i].w;
    }
    cout << sum << endl;
    for (int& i : edAns) {
        cout  << i << " ";
    }
}