#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define maxn 1010
int n,m,pre[maxn];
struct node
{
int x,y,z;
}e[maxn];
bool cmp(node p,node q)
{
return p.z<q.z;
}
int find(int x)
{
int r = x;
while(r!=pre[r])
r = pre[r];
int i=x,j;
while(pre[i]!=r)
{
j = pre[i];
pre[i] = r;
i = j;
}
return r;
}
void kruskal()
{
int num=0;
int sum = 0;
for(int i=0;i<m;i++)
{
int fx = find(e[i].x);
int fy = find(e[i].y);
if(fx!=fy)
{
sum += e[i].z;
pre[fx] = pre[fy];
num++;
}
}//加入边的数量等于n-1(顶点数减一),则无向图连通,生成最小生成树
if(num<n-1)//如果加入边的数量小于n - 1,则表明该无向图不连通,等价于不存在最小生成树
cout << "?" << endl;
else cout << sum << endl;
}
int main ()
{
while(cin >> n >> m)
{
for(int i=0;i<n;i++)
pre[i] = i;
for(int i=0;i<m;i++)
{
cin >> e[i].x >> e[i].y >> e[i].z;
e[i].x--,e[i].y--;
}
sort(e,e+m,cmp);
kruskal();
}
return 0;
}