// Slower C++ solution:
#include <bits/stdc++.h>

#define INF 1000000000

#define debug(x) cerr << #x << ": " << x << '\n';

using namespace std;

typedef long long ll;

typedef pair<int, int> P;



struct edge{

	int from, to, cost;

};



template <size_t N>

class UnionFindTree{

	private:

		int parent[N];

		int rank[N];

		int size[N];

	public:

		UnionFindTree(){

			for(int i = 0; i < (int) N; i++){

				parent[i] = i; rank[i] = 0; size[i] = 1;

			}

		}

		void init(){

			for(int i = 0; i < (int) N; i++){

				parent[i] = i; rank[i] = 0; size[i] = 1;

			}

		}

		int find(int x){

			if(parent[x] == x) return x;

			else return parent[x] = find(parent[x]);

		}

		void unite(int x, int y){

			x = find(x); y = find(y);

			if(x == y) return;



			if(rank[x] < rank[y]){

				parent[x] = y;

				size[y] += size[x];

			}else{

				parent[y] = x;

				size[x] += size[y];

				if(rank[x] == rank[y]) rank[x]++;

			}

		}

		bool same(int x, int y){

			return find(x) == find(y);

		}

		int getSize(int x){

			return size[find(x)];

		}

};



bool comp(const edge& e1, const edge& e2){

	return e1.cost < e2.cost;

}





int main(void){

	vector<edge> g;

	int V, E; cin >> V >> E;

	for(int i = 0; i < E; i++){

		int s, t, w; cin >> s >> t >> w;

		edge e = {s, t, w};

		g.push_back(e);

	}



	UnionFindTree<10000> uf;

	sort(g.begin(), g.end(), comp);

	ll res = 0;

	for(int i = 0; i < E; i++){

		int a = g[i].from, b = g[i].to;

		if(not uf.same(a, b)){

			uf.unite(a, b); res+= (ll) g[i].cost;

		}

	}



	cout << res << '\n';



	return 0;

}




// Faster C++ solution for the same problem (measured speedup: 1.1180756656958355x):
#include <bits/stdc++.h>

#define debug(x) cerr << #x << ": " << x << '\n';

using namespace std;

using ll = long long;

using P = pair<int, int>;

const int INF = (int)1e9;



// graph template

template<typename T>

class edge{

    public:

        int src, to; T cost;



        edge(){}

        edge(int to, T cost): src(-1), to(to), cost(cost){}

        edge(int src, int to, T cost): src(src), to(to), cost(cost){}

};

template<typename T>

using Edges = vector<edge<T>>;

template<typename T>

using AdjacencyList = vector<Edges<T>>;

template<typename T>

using Matrix = vector<vector<T>>;



#ifndef UNION_FIND_TREE

#define UNION_FIND_TREE

class UnionFindTree{

	private:

		int N;

		unique_ptr<int[]> parent;

		unique_ptr<int[]> rank;

		unique_ptr<int[]> size;

	public:

		UnionFindTree(int N){

			this -> N = N;

			this -> parent = make_unique<int[]>(N);

			this -> rank = make_unique<int[]>(N);

			this -> size = make_unique<int[]>(N);

			for(int i = 0; i < (int) N; i++){

				parent[i] = i; rank[i] = 0; size[i] = 1;

			}

		}

		void init(){

			for(int i = 0; i < (int) N; i++){

				parent[i] = i; rank[i] = 0; size[i] = 1;

			}

		}

		int find(int x){

			if(parent[x] == x) return x;

			else return parent[x] = find(parent[x]);

		}

		void unite(int x, int y){

			x = find(x); y = find(y);

			if(x == y) return;



			if(rank[x] < rank[y]){

				parent[x] = y;

				size[y] += size[x];

			}else{

				parent[y] = x;

				size[x] += size[y];

				if(rank[x] == rank[y]) rank[x]++;

			}

		}

		bool isSame(int x, int y){

			return find(x) == find(y);

		}

		int count(int x){

			return size[find(x)];

		}

};

#endif



template<typename T>

T kruskal(Edges<T> &edges, int V){

    sort(edges.begin(), edges.end(), [](const edge<T> &a, const edge<T> &b){return a.cost < b.cost;});

    UnionFindTree tree(V);

    

    T ret = 0;

    for(auto &e: edges){

        if(not tree.isSame(e.src, e.to)){

            ret += e.cost;

            tree.unite(e.src, e.to);

        }

    }



    return ret;

}



int main(void){

    int V, E;

    cin >> V >> E;



    Edges<int> edges(V);

    for(int i = 0; i < E; i++){

        int s, t, w;

        cin >> s >> t >> w;

        edges.emplace_back(edge<int>(s, t, w));

    }



    cout << kruskal(edges, V) << '\n';



    return 0;

}