#include "bits/stdc++.h"
using namespace std;

#define int long long
typedef pair<int, int> P;
#define mod 1000000007
#define INF (1LL<<60)

#define rep(i,n) for(int i=0, i##_len=(n); i<i##_len; ++i)
#define YES puts("YES")
#define Yes puts("Yes")
#define NO  puts("NO")
#define No  puts("No")
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }

const int dx[4] = { 1, 0, -1, 0 };
const int dy[4] = { 0, 1, 0, -1 };


struct UnionFind {
	vector< int > data;

	UnionFind(int sz) {
		data.assign(sz, -1);
	}

	bool unite(int x, int y) {
		x = find(x), y = find(y);
		if (x == y) return (false);
		if (data[x] > data[y]) swap(x, y);
		data[x] += data[y];
		data[y] = x;
		return (true);
	}

	int find(int k) {
		if (data[k] < 0) return (k);
		return (data[k] = find(data[k]));
	}

	int size(int k) {
		return (-data[find(k)]);
	}
};

// 重み付きグラフ
// できてほしいこと
// DFS, BFS, bellman-ford, サイクル検出、トポロジカルソートなど
template< typename T >
struct Graph {
	struct Edge {
		int from;
		int to;
		T weight;

		// weightが小さいほうが優先される
		friend bool operator< (const Graph::Edge& e, const Graph::Edge& f) {
			return e.weight > f.weight;
		}
	};

	vector<vector<Edge>> g;

	//------------
	// 初期化
	Graph(int N) { g.assign(N, vector<Edge>()); }

	//------------
	// 辺追加
	Edge add(int from, int to, T weight = 1) {
		Edge e = { from, to, weight };
		g[from].push_back(e);
		return e;
	}

	//------------
	// 深さ優先探索（startからの全点への距離を返す）
	vector<int> dfs(int start) {
		vector<int> dist(g.size(), INF);
		vector<int> seen(g.size(), false);

		stack<int> s;
		s.push(start);
		seen[start] = true;

		while (!s.empty()) {
			int u = s.top(); s.pop();
			for (Edge e : g[u]) {
				if (seen[e.to]) continue;
				else {
					dist[e.to] = min(dist[e.to], dist[u] + e.weight);
					s.push(e.to);
					seen[e.to] = true;
				}
			}
		}
		return dist;
	}

	//------------
	// 深さ優先探索（再帰バージョン）（startからの全点への距離を返す）
	void dfs2_internal(int v, vector<int>& seen, vector<T>& dist) {
		seen[v] = true;
		for (Edge e : g[v]) {
			if (seen[e.to]) continue;
			else {
				dist[e.to] = min(dist[e.to], dist[v] + e.weight);
				dfs2_internal(e.to, seen, dist);
				seen[e.to] = true;
			}
		}
	}
	vector<int> dfs2(int start) {
		vector<T> dist(g.size(), INF);
		vector<int> seen(g.size(), false);
		dist[start] = 0;
		dfs2_internal(start, seen, dist);

		return dist;
	}

	//------------
	// Dijkstra法（蟻本）
	// 到達できない頂点はINFを返す
	// verified: https://onlinejudge.u-aizu.ac.jp/courses/library/5/GRL/1/GRL_1_A
	vector<T> dijkstra(int start) {
		vector<T> dist(g.size(), INF);
		priority_queue<P, vector<P>, greater<P>> q;

		dist[start] = 0;
		q.push({ 0,start });
		while (!q.empty()) {
			int d, v;
			tie(d, v) = q.top(); q.pop();
			if (dist[v] < d) continue;  // 枝刈り
			for (Edge e : g[v]) {
				if (dist[e.to] <= dist[v] + e.weight) continue; // 枝刈り
				dist[e.to] = dist[v] + e.weight;
				q.push({ dist[e.to], e.to });
			}
		}
		return dist;
	}

	//------------
	// Bellman-Ford
	// 到達できない頂点はINFを返す
	// 負サイクルがあればnegative_cycle=trueを返す
	// verified: https://onlinejudge.u-aizu.ac.jp/courses/library/5/GRL/1/GRL_1_B
	pair<vector<T>, bool> BellmanFord(int start) {
		vector<T> dist(g.size(), INF);
		int N = g.size();

		dist[start] = 0;

		bool negative_cycle = false;

		rep(i, N) {
			rep(v, N) {
				for (Edge e : g[v]) {

					if (dist[v] != INF && dist[e.to] > dist[v] + e.weight) {
						dist[e.to] = dist[v] + e.weight;
						// N回目にも更新があれば負サイクルあり
						if (i == N - 1) {
							dist[e.to] = -INF;
							negative_cycle = true;
						}
					}
				}
			}
		}
		return { dist, negative_cycle };
	}

	//------------
	// Warshall-Floyd法
	// 到達できない頂点はINFを返す
	// verified: https://onlinejudge.u-aizu.ac.jp/courses/library/5/GRL/1/GRL_1_C
	vector<vector<T> > WarshallFloyd() {
		int N = g.size();
		vector<vector<T>> d(N, vector<T>(N, INF));

		rep(i, N) d[i][i] = 0;
		rep(i, N)for (auto e : g[i]) {
			d[i][e.to] = e.weight;
		}

		rep(k, N) {
			rep(i, N) {
				if (d[i][k] == INF) continue;
				rep(j, N) {
					if (d[k][j] == INF) continue;
					d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
				}
			}
		}
		return d;
	}

	//------------
	// Kruskal法（最小全域木）
	// 無向グラフのみに有効
	// 最小全域木のEdgeの集合と、重みの和を返す
	// verified: https://onlinejudge.u-aizu.ac.jp/courses/library/5/GRL/2/GRL_2_A
	pair<vector<Edge>, T> Kruskal() {
		int N = g.size();
		UnionFind uf(N);
		priority_queue<Edge> Q;

		rep(from, N) for (auto e : g[from])
			if (from < e.to) Q.push(e);

		T total = 0;
		vector<Edge> F;
		while (F.size() < N - 1 && !Q.empty()) {
			Edge e = Q.top(); Q.pop();
			if (uf.unite(e.from, e.to)) {
				F.push_back(e);
				total += e.weight;
			}
		}
		return make_pair(F, total);
	}

};


signed main() {

	int N, M;
	cin >> N >> M;
	Graph<int> g(N);
	rep(i, M) {
		int s, t, d;
		cin >> s >> t >> d;
		g.add(s, t, d);
		g.add(t, s, d);
	}

	vector<Graph<int>::Edge> result;
	int weight;
	tie(result, weight) = g.Kruskal();

	cout << weight << endl;

	return 0;
}



Measured memory_bytes: 13876