#ifndef EXODUS_graph_HPP
#define EXODUS_graph_HPP 1
#include<iostream>
#include <algorithm>
#include <array>
#include <cassert>
#include <functional>
#include <numeric>
#include <queue>
#include <tuple>
#include <vector>
#include <vector>
#include <algorithm>
#include <cassert>
namespace exodus{
namespace internal{
class nothing{
public:
nothing(){}
friend nothing operator +(const nothing&,const nothing&){return nothing();}
};
}
template<class info=internal::nothing>
struct dsu{
public:
struct node{
public:
node(){anc=-1,siz=1,ifo=info();}
int anc,siz;
info ifo;
};
dsu():_n(0){}
explicit dsu(int n){
_n=n;node.resize(n);
for(int i=0;i<n;i++)
node[i].anc=i;
}
void build(int n){
_n=n;
node.clear();
node.resize(n);
for(int i=0;i<n;i++)
node[i].anc=i;
}
int find(int u){
assert(0<=u&&u<_n);
return __find(u);
}
void merge(int u,int v){
assert(0<=u&&u<_n);
assert(0<=v&&v<_n);
u=__find(u),v=__find(v);
if(node[u].siz<node[v].siz)std::swap(u,v);
node[v].anc=u;
node[u].ifo=node[u].ifo+node[v].ifo;
}
bool check(int u,int v){
assert(0<=u&&u<_n);
assert(0<=v&&v<_n);
u=__find(u),v=__find(v);
return u==v;
}
private:
int _n;
std::vector<node>node;
int __find(int u){
return u==node[u].anc?u:node[u].anc=__find(node[u].anc);
}
};
}
#include "atcoder/twosat"
namespace exodus{
struct scc_graph{
public:
scc_graph(){n=0;}
explicit scc_graph(int _n){n=_n;g.resize(n);}
std::vector<std::pair<int,int>>edges(){return edge;}
std::pair<int,int>get_edge(int i){
assert(0<=i&&i<(int)edge.size());
return edge[i];
}
void add_edge(int u,int v){
assert(0<=u&&u<n);
assert(0<=v&&v<n);
g[u].emplace_back(v);
edge.emplace_back(u,v);
}
std::vector<std::vector<int>> scc(){
std::vector<int>dfn(n,0),low(n,0),stk,ins(n,0),col(n,-1);
int dfx=0,col_cnt=0;
stk.reserve(n);
std::function<void(int)>tarjan=[&](int u){
dfn[u]=low[u]=++dfx;ins[u]=true;stk.emplace_back(u);
for(auto v:g[u])
if(!dfn[v])tarjan(v),low[u]=std::min(low[u],low[v]);
else if(ins[v])low[u]=std::min(low[u],dfn[v]);
if(dfn[u]==low[u]){
while(u!=stk.back())col[stk.back()]=col_cnt,ins[stk.back()]=false,stk.pop_back();
col[stk.back()]=col_cnt,ins[stk.back()]=false,stk.pop_back();
++col_cnt;
}
};
for(int i=0;i<n;i++)
if(!dfn[i])
tarjan(i);
std::vector<std::vector<int>>res(col_cnt);
for(int i=0;i<n;i++)
res[col[i]].emplace_back(i);
return res;
}
private:
int n;
std::vector<std::vector<int>>g;
std::vector<std::pair<int,int>>edge;
};
struct toposort_graph{
public:
toposort_graph(){n=0;}
explicit toposort_graph(int _n){n=_n;g.resize(n);}
std::vector<std::pair<int,int>>edges(){return edge;}
std::pair<int,int>get_edge(int i){
assert(0<=i&&i<(int)edge.size());
return edge[i];
}
void add_edge(int u,int v){
assert(0<=u&&u<n);
assert(0<=v&&v<n);
g[u].emplace_back(v);
edge.emplace_back(u,v);
}
std::vector<int> toposort(){
std::vector<int>res;
std::vector<int>deg(n,0);
res.reserve(n);
for(auto [u,v]:edge)deg[v]++;
std::queue<int>q;
for(int i=0;i<n;i++)
if(deg[i]==0)
q.emplace(i);
while(!q.empty()){
int u=q.front();q.pop();
res.emplace_back(u);
for(auto v:g[u])
if(!(--deg[v]))
q.emplace(v);
}
assert((int)res.size()==n);
return res;
}
private:
int n;
std::vector<std::vector<int>>g;
std::vector<std::pair<int,int>>edge;
};
template<typename T>
struct mst_graph{
public:
mst_graph(){n=0;}
explicit mst_graph(int _n){n=_n;g.resize(n);}
std::vector<std::tuple<int,int,T>>edges(){return edge;}
std::tuple<int,int,T>get_edge(int i){
assert(0<=i&&i<(int)edge.size());
return edge[i];
}
void add_edge(int u,int v,T w){
assert(0<=u&&u<n);
assert(0<=v&&v<n);
g[u].emplace_back(v,w);
g[v].emplace_back(u,v);
edge.emplace_back(u,v,w);
}
std::vector<std::tuple<int,int,T>> mst(){
std::vector<int>ord(edge.size());
std::iota(ord.begin(),ord.end(),0);
std::sort(ord.begin(),ord.end(),
[&](int lhs,int rhs){return std::get<2>(edge[lhs])<std::get<2>(edge[rhs]);});
exodus::dsu dsu(n);
std::vector<std::tuple<int,int,T>>res;
for(int i=0;i<(int)ord.size();i++){
auto [u,v,w]=edge[ord[i]];
if(dsu.check(u,v))continue;
dsu.merge(u,v);
res.emplace_back(u,v,w);
}
return res;
}
private:
int n;
std::vector<std::vector<std::pair<int,T>>>g;
std::vector<std::tuple<int,int,T>>edge;
};
struct twosat_graph{
public:
twosat_graph(){n=0;}
explicit twosat_graph(int _n){
n=_n;
ans.resize(n);
g.resize(2*n);
idx.resize(n);
for(int i=0;i<n;i++)
idx[i][0]=i,idx[i][1]=i+n;
}
void add_clause(int u,bool a,int v,bool b){
add_edge(idx[u][a^1],idx[v][b]);
add_edge(idx[v][b^1],idx[u][a]);
}
bool possible(){
std::vector<int>dfn(2*n,0),low(2*n,0),stk,ins(2*n,0),col(2*n,-1);
int dfx=0,col_cnt=0;
stk.reserve(2*n);
std::function<void(int)>tarjan=[&](int u){
dfn[u]=low[u]=++dfx;ins[u]=true;stk.emplace_back(u);
for(auto v:g[u])
if(!dfn[v])tarjan(v),low[u]=std::min(low[u],low[v]);
else if(ins[v])low[u]=std::min(low[u],dfn[v]);
if(dfn[u]==low[u]){
while(u!=stk.back())col[stk.back()]=col_cnt,stk.pop_back();
col[stk.back()]=col_cnt,stk.pop_back();
++col_cnt;
}
};
for(int i=0;i<n;i++)
if(!dfn[i])
tarjan(i);
for(int i=0;i<n;i++)
if(col[idx[i][0]]==col[idx[i][1]])
return false;
for(int i=0;i<n;i++)
ans[i]=dfn[idx[i][0]]<dfn[idx[i][1]];
return true;
}
std::vector<int> answer(){return ans;}
private:
void add_edge(int u,int v){
g[u].emplace_back(v);
}
int n;
std::vector<int>ans;
std::vector<std::array<int,2>>idx;
std::vector<std::vector<int>>g;
};
}
#endif // EXODUS_graph_HPP
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
cin.tie(nullptr)->sync_with_stdio(false);
int n,m;
cin>>n>>m;
exodus::mst_graph<ll> mst(n);
for(int i=0,u,v,w;i<m;i++){
cin>>u>>v>>w;
mst.add_edge(u-1,v-1,w);
}
ll ans=0;
auto edges=mst.mst();
for(auto &[u,v,w]:edges)
ans+=w;
cout<<ans<<'\n';
return 0;
}