#include <bits/stdc++.h>
#define ll long long
#define INF 1000000005
#define MOD 1000000007
#define EPS 1e-10
#define rep(i,n) for(int i=0;i<(int)(n);++i)
#define rrep(i,n) for(int i=(int)(n)-1;i>=0;--i)
#define srep(i,s,t) for(int i=(int)(s);i<(int)(t);++i)
#define each(a,b) for(auto& (a): (b))
#define all(v) (v).begin(),(v).end()
#define len(v) (int)(v).size()
#define zip(v) sort(all(v)),v.erase(unique(all(v)),v.end())
#define cmx(x,y) x=max(x,y)
#define cmn(x,y) x=min(x,y)
#define fi first
#define se second
#define pb push_back
#define show(x) cout<<#x<<" = "<<(x)<<endl
#define sar(a,n) {cout<<#a<<":";rep(pachico,n)cout<<" "<<a[pachico];cout<<endl;}

using namespace std;

template<typename S,typename T>auto&operator<<(ostream&o,pair<S,T>p){return o<<"{"<<p.fi<<","<<p.se<<"}";}
template<typename T>auto&operator<<(ostream&o,set<T>s){for(auto&e:s)o<<e<<" ";return o;}
template<typename S,typename T,typename U>
auto&operator<<(ostream&o,priority_queue<S,T,U>q){while(!q.empty())o<<q.top()<<" ",q.pop();return o;}
template<typename K,typename T>auto&operator<<(ostream&o,map<K,T>&m){for(auto&e:m)o<<e<<" ";return o;}
template<typename T>auto&operator<<(ostream&o,vector<T>v){for(auto&e:v)o<<e<<" ";return o;}
void ashow(){cout<<endl;}template<typename T,typename...A>void ashow(T t,A...a){cout<<t<<" ";ashow(a...);}
template<typename S,typename T,typename U>
struct TRI{S fi;T se;U th;TRI(){}TRI(S f,T s,U t):fi(f),se(s),th(t){}
bool operator<(const TRI&_)const{return(fi==_.fi)?((se==_.se)?(th<_.th):(se<_.se)):(fi<_.fi);}};
template<typename S,typename T,typename U>
auto&operator<<(ostream&o,TRI<S,T,U>&t){return o<<"{"<<t.fi<<","<<t.se<<","<<t.th<<"}";}

typedef pair<int, int> P;
typedef pair<ll, ll> pll;
typedef TRI<int, int, int> tri;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef vector<P> vp;
typedef vector<double> vd;
typedef vector<string> vs;

const int MAX_N = 100005;

class UnionFind {
private:
    int sz;
    vector<int> par, nrank;
public:
    UnionFind(){}
    UnionFind(int node_size) : sz(node_size), par(sz), nrank(sz, 0){
        iota(par.begin(), par.end(), 0);
    }
    int find(int x){
        if(par[x] == x) return x;
        else return par[x] = find(par[x]);
    }
    int unite(int x, int y){
    	if(nrank[x] < nrank[y]) swap(x,y);
        par[y] = x;
        if(nrank[x] == nrank[y]) nrank[x]++;
        return x;
    }
};

template<typename T> class Boruvka {
private:
    struct edge {
        int to;
        T cost;
        typename list<edge>::iterator rev;
        edge(const int _to, const T _cost)
            : to(_to), cost(_cost){}
        edge(const int _to, const T _cost, const typename list<edge>::iterator _rev)
            : to(_to), cost(_cost), rev(_rev){}
    };
    struct info {
        T cost;
        typename list<edge>::iterator itr;
        info(){}
        info(const T _cost) : cost(_cost){}
        info(const T _cost, const typename list<edge>::iterator _itr) : cost(_cost), itr(_itr){}
    };
    const int V;
    const T inf;
    UnionFind uf;
    vector<list<edge> > G;
    void cleanup(vector<int>& rem_node, vector<info>& check){
        vector<int> tmp;
        for(int v : rem_node){
            for(auto it = G[v].begin(); it != G[v].end();){
                const edge& e = *it;
                const int p = uf.find(e.to);
                if(p == v){
                    it = G[v].erase(it);
                    continue;
                }
                if(e.cost < check[p].cost){
                    if(check[p].cost == inf) tmp.push_back(p);
                    else G[p].erase(check[p].itr->rev), G[v].erase(check[p].itr);
                    check[p].cost = e.cost, check[p].itr = it++;
                }else{
                    G[p].erase(e.rev), it = G[v].erase(it);
                }
            }
            for(int id : tmp) check[id].cost = inf;
            tmp.clear();
        }
    }
    bool contract(vector<int>& rem_node, vector<int>& check){
        vector<int> new_rem_node;
        bool update = false;
        for(int v : rem_node){
            const int p = uf.find(v);
            if(G[p].empty() || p != v) continue;
            T mn = inf; int id = -1;
            for(const edge& e : G[v]){
                if(e.cost < mn) mn = e.cost, id = e.to;
            }
            id = uf.find(id);
            if(id != v){
                ans += mn, update = true, uf.unite(id, v);
            }
        }
        for(int v : rem_node){
            const int p = uf.find(v);
            if(v == p) new_rem_node.push_back(v);
            else G[p].splice(G[p].begin(), move(G[v]));
        }
        swap(rem_node, new_rem_node);
        return update;
    }

public:
    T ans;
    Boruvka(const int node_size)
        : V(node_size), inf(numeric_limits<T>::max()), uf(V), G(V), ans(0){}
    void add_edge(const int from, const int to, const T cost){
        G[from].emplace_back(to, cost);
        G[to].emplace_back(from, cost, --G[from].end());
        G[from].back().rev = --G[to].end();
    }
    T solve(){
        vector<int> rem_node(V), erase_check(V, 0);
        iota(rem_node.begin(), rem_node.end(), 0);
        vector<info> multiple_check(V);
        for(int i = 0; i < V; ++i) multiple_check[i] = info(inf);
        while((int)rem_node.size() > 1){
            if(!contract(rem_node, erase_check)) return inf;
            cleanup(rem_node, multiple_check);
        }
        return ans;
    }
};

int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    int n, m;
    cin >> n >> m;
    Boruvka<int> br(n);
    for(int i = 0; i < m; ++i){
        int a, b, c;
        cin >> a >> b >> c;
        br.add_edge(a, b, c);
    }
    cout << br.solve() << "\n";
}



Measured memory_bytes: 12868