#include <bits/stdc++.h>
using namespace std;
#define MAX 200
#define INF 0x7fffffff
//无向图,规定两点间只有一条边
//下标从1开始
int graph[MAX+1][MAX+1],points[MAX+1],numbian,numdian,circle[MAX+1];
struct mydata
{
int from,to,cost;
mydata(int from,int to,int cost){
this->from=from;
this->to=to;
this->cost=cost;
}
mydata(){}
bool operator<(const mydata &a){
return this->cost<a.cost;
}
};
void prim(){
//加点法
mydata data[MAX+1];
int ok[MAX+1];
memset(ok,0,sizeof(ok));
for(int i=1;i<=numdian;i++){
data[i].from=i;
data[i].to=1;
data[i].cost=graph[i][1];
}
//从点1开始
ok[1]=1;
for(int i=0;i<numdian-1;i++){
int min=-1;
for(int j=2;j<=numdian;j++){
//注意这里
if(!ok[j]&&(min==-1||data[j].cost<data[min].cost)) min=j;
}
for(int j=2;j<=numdian;j++){
//其他所有没加进去的点和这个点的距离
if(!ok[j]&&graph[min][j]!=-1&&graph[min][j]<data[j].cost){
data[j].cost=graph[min][j];
data[j].to=min;
}
}
ok[min]=1;
cout<<data[min].from<<" -> "<<data[min].to<<endl;
}
//结果即在data中,有n-1条边
}
//适合稀疏的图
void kruskal(){
//加边法
int circle[MAX+1],k=1;//区分连通分量
mydata data[MAX+1];
for(int i=1;i<=numdian;i++) circle[i]=i;
for(int i=1;i<=numdian;i++){
for(int j=i;j<=numdian;j++){
if(graph[i][j]!=INF){
data[k].cost=graph[i][j];
data[k].from=i;
data[k].to=j;
k++;
}
}
}
sort(data+1,data+numbian+1);
//注意左闭右开区间形式
for(int i=1;i<=numbian;i++){
if(circle[data[i].from]!=circle[data[i].to]){
cout<<data[i].from<<" -> "<<data[i].to<<endl;
//更新circle
int p=circle[data[i].to];
for(int j=1;j<=numdian;j++){
//反例!!,这样写不可以,因为circle[data[i].to]的值动态变化,不能比较
//目的:把所有是to的分量改为from的,但是to本身也会变呀,导致后续比较都错误
// if(circle[j]==circle[data[i].to])
// circle[j]=circle[data[i].from];
if(circle[j]==p)
circle[j]=circle[data[i].from];
}
}
}
}
//kruskal更新一次,查找多次的版本
//并查集
// int func(int x){
// //这是一个技巧,因为连通分量数组circle初始化为
// //1 2 ... n,即为每个点的编号,那么对于circle[i]有
// //(1)circle[i]==i,代表他就没变过,所以连通分量编号就是i
// //(2)circle[i]!=i,代表被合并了,i合并到了circle[i]里,在返回(1)看看;
// //返回值是对应的circle[i]==i的点,修改的也是这个点;
// //并且修改所有的circle[x] != x的点;
// return circle[x] == x ? x : circle[x] = func(circle[x]);
// }
// //改成循环
// int func1(int x){
// if(circle[x] == x) return x;
// int k=x;
// int *changes=new int[numdian],index=0;
// while (circle[k] != k)
// {
// changes[index++]=k;
// k=circle[k];
// }
// for(int i=0;i<index;i++) circle[changes[i]]=k;
// delete[] changes;
// return circle[k];
// }
// bool find(int x,int y){
// //找是否在同一个连通分量
// //circle按照点的顺序创建
// return func(x)==func(y);
// }
// void kruskal(){
// //加边法
// int k=1;//区分连通分量
// mydata data[MAX+1];
// for(int i=1;i<=numdian;i++) circle[i]=i;
// for(int i=1;i<=numdian;i++){
// for(int j=i;j<=numdian;j++){
// if(graph[i][j]!=INF){
// data[k].cost=graph[i][j];
// data[k].from=i;
// data[k].to=j;
// k++;
// }
// }
// }
// sort(data+1,data+numbian+1);
// //注意左闭右开区间形式
// for(int i=1;i<=numbian;i++){
// if(!find(data[i].from,data[i].to)){
// cout<<data[i].from<<" -> "<<data[i].to<<endl;
// circle[data[i].to]=circle[data[i].from]
// }
// }
// }
int main(){
cin>>numdian>>numbian;
for(int i=1;i<=numdian;i++)
for(int j=1;j<=numdian;j++)
graph[i][j]=INF;
for(int i=1;i<=numdian;i++) points[i]=i;
for(int i=0;i<numbian;i++){
int x,y,z;
cin>>x>>y>>z;
graph[x][y]=graph[y][x]=z;
}
for(int i=1;i<=numdian;i++){
for(int j=1;j<=numdian;j++)
cout<<graph[i][j]<<" ";
cout<<endl;
}
// prim();
kruskal();
return 0;
}