#include <stdio.h>
#include <algorithm>
#include <utility>
#include <functional>
#include <cstring>
#include <queue>
#include <stack>
#include <math.h>
#include <iterator>
#include <vector>
#include <string>
#include <set>
#include <math.h>
#include <iostream>
#include <random>
#include<map>
#include <iomanip>
#include <time.h>
#include <stdlib.h>
#include <list>
#include <typeinfo>
#include <list>
#include <set>
#include <cassert>
#include<fstream>
#include <unordered_map>
#include <cstdlib>
#include <complex>
#include <cctype>
#include <bitset>

using namespace std;
using ll = long long;
using pll = pair<long long, long long>;
using vll = vector<long long>;
#define rep(i,n) for(long long i(0);(i)<(n);(i)++)
ll INF = 1LL << 60;

struct UnionFind{
    vll par;
    vll rank;
    //* member functions
    UnionFind(ll n){
        par = vll(n);
        rank = vll(n, 0);
        for(ll i=0; i< n; i++) par[i] = i;
    }

    int find(ll x){
        if(par[x] == x) return x;
        else{
            return par[x] = find(par[x]);
        }
    }

    void unite(ll x, ll y){
        x = find(x); y = find(y);
        if(x == y) return;

        if(rank[x] < rank[y]){
            par[x] = y; //* new root is y
        }
        else {
            par[y] = x; //* new root is x
            if(rank[x] == rank[y]) rank[x] ++;
        }
    }

    bool same(ll x, ll y){
        return find(x) == find(y);
    }
};

struct edge{
    ll to;
    ll cost;
    edge(ll t, ll c){
        to = t;
        cost = c;
    }
};

struct fulledge{
    ll u, v, cost;
    fulledge(ll _u, ll _v, ll _c){
        u = _u; v = _v; cost = _c;
    }
    bool operator < (fulledge e){
        return this->cost < e.cost;
    }
};

struct edgeGreater{
    bool operator()(const fulledge &e1, const fulledge &e2){
        return e1.cost > e2.cost;
    }
};

ll Prim(vector<vector<edge> > &G, ll s){
    ll N = G.size(), total_cost = 0;
    vector<bool> used(N);
    priority_queue<fulledge, vector<fulledge>, edgeGreater> que;
    //* initialize heap
    for(ll i=0; i< G[s].size(); i++){
        que.push(fulledge(s, G[s][i].to, G[s][i].cost));
    }
    used[s] = true;
    //* 
    while(!que.empty()){
        fulledge e = que.top(); que.pop();
        if(used[e.u] && used[e.v]) 
            continue;
        //* now e is in the fromtier
        total_cost += e.cost;
        ll u = e.u; if(used[u]) u = e.v; 
        used[u] = true;
        //* scan through u's neighbours
        for(ll i=0; i< G[u].size(); i++){
            if(used[G[u][i].to]) continue;
            //* push new edges into que
            que.push(fulledge(u, G[u][i].to, G[u][i].cost));
        }
    }
    return total_cost;
}

ll Kruskal(vector<vector<edge> > &G){
    ll N = G.size();
    vector<fulledge> elist;
    rep(i, N){
        rep(j, G[i].size()){
            elist.push_back(fulledge(i, G[i][j].to, G[i][j].cost));
        }
    }
    sort(elist.begin(), elist.end());
    //*  Union Find Tree is on vertex, which is integer
    UnionFind uf(N);
    ll total_cost = 0;
    rep(i, elist.size()){
        fulledge e = elist[i];
        if(!uf.same(e.u, e.v)){
            uf.unite(e.u, e.v);
            total_cost += e.cost;
        }
    }
    return total_cost;
}

int main(){
    ll N, M ;
    cin >> N >> M;
    vll A(M), B(M), C(M);
    for(ll i=0; i < M; i++){
        cin >> A[i] >> B[i] >> C[i];
    }
    vector<vector<edge> > Glist(N);
    for(ll i=0; i< M; i++){
        Glist[A[i]].push_back(edge(B[i], C[i]));
        Glist[B[i]].push_back(edge(A[i], C[i]));
    }
    //* ll res = Prim(Glist, 0);
    ll res = Kruskal(Glist);

    cout << res << endl;
    return 0;
}


Measured memory_bytes: 16864