using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Threading.Tasks;

/*
・概要
	最小全域木の重みの和を求めるプログラム。
	最小全域木とは、全域木を構成する辺のコストの総和が最小となるもののこと。
	（グラフが連結で（全域木）、かつ辺のコストの合計が最小）
	対象は無向グラフとする。

・実装
	クラスカルのアルゴリズムを使用（Kruskal））
	実装には、UnionFindを使用する 。

・使用法
	1. MSTクラスのインスタンスを作成
	引数は、頂点の数と辺の数
	（e.g. MST graph = new MST(V,E);）

	2. 各辺を加える
	引数は、始点、終点、重み
	（e.g. graph.AddEdge(start,goal,value);）

	3. Kruskalのアルゴリズムを作動させ、重みを取得
	（e.g. long ans = graph.Kluskal();）

	4. 選択された辺のリストを取得
	（e.g. List<MST.Edge> ls = graph.SelectedEdge();）

*/

class Program
{
	static int N, K, L;
	//所謂Union Find木を配列で表現
	//それぞれインデックスの根を格納する
	static UnionFind ufRoad;
	static UnionFind ufRail;

	//Main
	static void Main(string[] args)
	{
		//読み込む
		string[] S = Console.ReadLine().Split(' ');
		//頂点の数
		int V = int.Parse(S[0]);
		//辺の数
		int E = int.Parse(S[1]);

		//1. MSTクラスのインスタンスを作成
		//引数は、頂点の数と辺の数
		MST graph = new MST(V,E);

		//辺を読み込んで加える
		for(int i=0; i<E; i++){
			string[] line = Console.ReadLine().Split(' ');
			int start = int.Parse(line[0]);
			int goal = int.Parse(line[1]);
			//重みはlongなので注意
			long value = long.Parse(line[2]);

			//2. 各辺を加える
			//引数は、始点、終点、重み（int,int,long）
			graph.AddEdge(start,goal,value);
		}

		//3. Kruskalのアルゴリズムを作動させ、重みを取得
		long ans = graph.Kluskal();
		Console.WriteLine(ans);

		/*
		//4. 選択された辺のリストを取得
		List<MST.Edge> ls = graph.SelectedEdge();
		foreach(MST.Edge e in ls){
			int a = e.Start;
			int b = e.Goal;
			long cost = e.Value;
			Console.WriteLine("{0} to {1} ,cost{2}", a, b, cost);
		}
		*/
	}
}


//最小全域木を作成するクラス（Klaskalのアルゴリズムを使用）
//各辺の重みはlongとする。（但し、UnionFindと同様インデックスはintに限定。）
class MST{

	//辺を保存する構造体（cf. 各頂点の情報は、UnionFindで管理）
	/*
	cf. 今回は無向グラフが対象なので、始点終点はどちらでも良い
	*/
	public struct Edge
	{
		//始点
		public int Start;
		//終点
		public int Goal;
		//重み（負数もOK）
		public long Value;

		public Edge(int s, int g, long v){
			this.Start = s;
			this.Goal = g;
			this.Value = v;
		}

		//Sort用のcomparer
		/*
		重みの大きさでソートするようにする（昇順ソート）
		*/
		public static int Compare(Edge a, Edge b)
		{
			long va = a.Value;
			long vb = b.Value;

			if (va > vb){
				return 1;
			}
			else if(va < vb){
				return -1;
			}
			else{
				return 0;
			}
		}
	}

	//頂点数と辺の数
	int V, E;
	//辺を格納するリスト
	List<Edge> edgeList;
	//頂点を格納するUnionFind
	UnionFind tree;
	//最終的に残った辺だけを格納するリスト
	public List<Edge> selectedEdge;
	//最小値
	public long minCost;

	//コンストラクタ
	//引数は、頂点の数と辺の数
	public MST(int vertex, int edge){
		this.V = vertex;
		this.E = edge;
		this.edgeList = new List<Edge>();
		this.selectedEdge = new List<Edge>();
		this.tree = new UnionFind(V);
	}

	//辺を追加するメソッド
	/*
	引数は、始点、終点、重み
	但し、無向グラフなので、始点終点は気にせずとも良い
	*/
	public void AddEdge(int start, int goal, long value){
		Edge edge = new Edge(start, goal, value);
		this.edgeList.Add(edge);
	}

	//最小全域木を求める関数
	/*
	重みの総和を返す。
	また、選択された辺だけを格納したリストも作成
	*/
	public long Kluskal(){
		//先ず、辺をコストの低い順にソート
		this.edgeList.Sort(Edge.Compare);

		//最小値を初期化
		this.minCost = 0;

		//コストの低い順に辺を見ていく（貪欲）
		for(int i=0; i<this.E; i++){
			//対象となる辺の情報
			Edge edge = this.edgeList[i];
			int a = edge.Start;
			int b = edge.Goal;
			long cost = edge.Value;

			//辺edgeを加えても閉路を作らない場合、採用
			/*
			閉路ができるかどうかは、根が同じでないかどうかを判定すれば分かる
			*/
			if(!this.tree.SameRoot(a,b)){
				//この辺を採用し、コストに反映
				this.selectedEdge.Add(edge);
				this.minCost += cost;

				//辺を繋ぐ（UnionFindに反映）
				this.tree.Merge(a,b);
			}
		}

		//最小コストを返す
		return this.minCost;
	}

	//選択された辺のリストを取得するメソッド
	public List<Edge> SelectedEdge(){
		return this.selectedEdge;
	}
}


//自作クラス
//ジェネリック（タイプを指定可能）ではなく、取り敢えずintに限定して作成
class UnionFind
{
		public readonly int[] Array;

		public UnionFind(int capacity)
		{
				this.Array = new int[capacity];

				//自身を根として初期化
				for(int i=0; i<capacity; i++){
						this.Array[i] = i;
				}
		}

		//unionFindへの追加（インデックスaとインデックスbの根を同一にする）
		public void Merge(int a, int b){
			//根を決定
			int rootA = Root(a);
			int rootB = Root(b);
			int newRoot = Math.Min(rootA, rootB);

			//より小さい方に根を変化させる
			this.Array[rootA] = newRoot;
			this.Array[rootB] = newRoot;
		}

		//根を判定し、出力
		public int Root(int ind){
			int root = 0;

			if(this.Array[ind] == ind){
					root = ind;
			}else{
					root = Root(this.Array[ind]);
			}

			//根をその場で更新しつつ、出力もする（一種のメモ化再帰とも）
			return this.Array[ind] = root;
		}

		//2つのインデックスの根が同一なら、trueを返す
		public bool SameRoot(int a, int b){
			return this.Root(a) == this.Root(b);
		}
}



Measured memory_bytes: 25828