#include <iostream>
#include <cassert>
#define GET_MACRO(_1,_2,_3,_4,_5,_6,_7,_8,NAME,...) NAME
#define pr(...) std::cerr<< GET_MACRO(__VA_ARGS__,pr8,pr7,pr6,pr5,pr4,pr3,pr2,pr1)(__VA_ARGS__) <<std::endl
#define pr1(a) (#a)<<"="<<(a)<<" "
#define pr2(a,b) pr1(a)<<pr1(b)
#define pr3(a,b,c) pr1(a)<<pr2(b,c)
#define pr4(a,b,c,d) pr1(a)<<pr3(b,c,d)
#define pr5(a,b,c,d,e) pr1(a)<<pr4(b,c,d,e)
#define pr6(a,b,c,d,e,f) pr1(a)<<pr5(b,c,d,e,f)
#define pr7(a,b,c,d,e,f,g) pr1(a)<<pr6(b,c,d,e,f,g)
#define pr8(a,b,c,d,e,f,g,h) pr1(a)<<pr7(b,c,d,e,f,g,h)
#define prArr(a) {cerr<<(#a)<<"={";int i=0;for(auto t:(a))cerr<<(i++?", ":"")<<t;cerr<<"}"<<endl;}

template<typename T>
class Deque{
public:
  T *val;
  int sz;
  int length;
  int head, tail;
  
  Deque(){sz = 0, length = 0, val = new T[0];}
  Deque(int size, T init = T()){
    length = 1;
    while(length < size) length *= 2;
    val = new T[length];
    sz = size;
    head = 0;
    tail = 0;
    for(int i=0;i<size;i++) val[tail++] = init;
  }
  
  Deque(const Deque &t){
    sz = t.size();
    length = 1;
    while(length < sz) length *= 2;
    val = new T[length];
    head = 0;
    tail = 0;
    for(int i=0;i<sz;i++) val[tail++] = t[i];
  }

  ~Deque(){
    delete [] val;
  }
  
  Deque<T> operator = (const Deque<T> t){
    delete [] val;
    sz = t.size();
    length = 1;
    while(length < sz) length *= 2;
    val = new T[length];
    head = 0;
    tail = 0;
    for(int i=0;i<sz;i++) val[tail++] = t[i];
    return t;
  };
  
  T& operator [] (int i){
    assert(0 <= i && i < sz);
    return head+i<length? val[head+i]:val[head+i - length];
  }
  T& operator [] (int i)const{
   assert(0 <= i && i < sz);
    return head+i<length? val[head+i]:val[head+i - length];
  }
  
  bool operator == (const Deque<T> &a)const{
    if(size() != a.size()) return 0;
    for(int i=0;i<size();i++)
      if(!((*this)[i] == a[i])) return 0;
    return 1;
  }
  
  T& front(){return val[head];}
  T& back(){return val[(head + sz - 1) % length];}
  
  int size()const{return sz;}
  int empty()const{return sz == 0;}
  
  void resize(int n, T v=T()){
    while(n < size()) pop_back();
    while(n > size()) push_back_fast(v);
  }


  void increaseMemory(){
    int length_ = length;
    length = length * 2;
    if(length == 0) length = 1;
    T *val2 = new T[length];
    for(int i=0;i<sz;i++) val2[i] = val[(head + i)%length_];
    head = 0;
    tail = sz;
    delete [] val;
    val = val2;
  };
  
  void push_back(T v){
    if(sz == length) increaseMemory();
    sz++;
    val[tail++] = v;
    if(tail == length) tail = 0;
  }

  void push_back_fast(T &v){
    if(sz == length) increaseMemory();
    sz++;
    val[tail++] = v;
    if(tail == length) tail = 0;
  }
    
  void push_front(int v){
    if(sz == length) increaseMemory();
    sz++;
    head--;
    if(head == -1) head = length - 1;
    val[head] = v;
  }

  void pop_back(){
    assert(sz > 0);
    sz--;
    tail--;
    if(tail == -1) tail = length - 1;
  }
  
  void pop_front(){
    assert(sz > 0);
    sz--;
    head++;
    if(head == length) head = 0;
  }

  friend std::ostream& operator << (std::ostream& os,const Deque<T> a){
    os<<"[";
    for(int i=0;i<a.size();i++){
      if(i) os<<" ";
      os<<a[i];
    }
    os<<"]";
    return os;
  }
};

template <typename T>
using vector = Deque<T>;

class UF{
public:
  int V;
  vector<int> par,rnk,sz;
  UF(){}
  UF(int V):V(V),par(V),rnk(V,0),sz(V,1){for(int i=0;i<V;i++)par[i]=i;}
  
  int find(int x){
    assert(x < V);
    if(par[x]==x)return x;
    return par[x]=find(par[x]);
  }
  
  void unite(int x,int y){
    x=find(x), y=find(y);
    if(x==y)return;
    if(rnk[x]<rnk[y])par[x]=y, sz[y] += sz[x];
    else{
      par[y]=x;
      sz[x] += sz[y];
      if(rnk[x]==rnk[y])rnk[x]++;
    }
  }

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

  int size(int x){return sz[find(x)];}
};


template<class T, class D>
void sort(D &A, int n){
  auto tmp = new T[n];
  
  auto dfs=[&](int l, int r, auto f){
    if(l + 1 >= r) return;
    int m = (l + r) / 2;
    f(l, m, f);
    f(m, r, f);
    
    int idx = 0;
    int i = l;
    int j = m;
    while(i < m || j < r){
      if(i >= m) tmp[idx] = A[j++];
      else if(j >= r) tmp[idx] = A[i++];
      else if(A[i] < A[j]) tmp[idx] = A[i++];
      else tmp[idx] = A[j++];
      idx++;
    }
    for(int i=0;i<r-l;i++) A[l + i] = tmp[i];
    
  };
  
  dfs(0, n, dfs);
  delete[] tmp;
}

struct data{
  int a, b, c;
  data(){}
  data(int a, int b, int c):a(a), b(b), c(c){}
  bool operator < (data d)const{return c < d.c;}
};



long long kruskal(int N, vector<data> edge){
  UF uf(N);
  int M = edge.size();
  sort<data>(edge, M);
  long long res = 0;
  for(int i=0;i<M;i++){
    auto e = edge[i];
    if(uf.same(e.a, e.b)) continue;
    uf.unite(e.a, e.b);
    res += e.c;
  }
  return res;
}

signed main(){

  int N, M;
  std::cin>>N>>M;

  vector<data> edge(M);
  for(int i=0;i<M;i++){
    int a, b, c;
    std::cin>>a>>b>>c;
    edge[i] = data(a, b, c);
  }
  long long ans = kruskal(N, edge);
  std::cout<<ans<<std::endl;
  
  
  return 0;
}



Measured memory_bytes: 6452