"""
https://www.lanqiao.cn/problems/889/learning/?page=1&first_category_id=1&problem_id=889
"""
"""
贪心思路: 将所有的边按照边的长度从小到大排序, 每次取出最短的那条边,
如果加入到生成树后不会成环就加入, 否则就改造(不要)这条路
"""
n, m = map(int, input().split())
# 使用普通列表存图
e = []
for _ in range(m):
u, v, w = map(int, input().split())
e.append((w, u, v))
# 1.将所有边按照边的长度从小到大进行排序
e.sort()
# p记录每个节点的祖宗节点
p = list(range(n + 1))
def find_root(x):
if x != p[x]:
p[x] = find_root(p[x])
return p[x]
# 记录最大的那条路的分值
ans = 0
# 贪心: 每次取出的道路的分值都是最小的, 作出最佳的决策
for w, u, v in e:
rootu = find_root(u)
rootv = find_root(v)
"""
u、v不属于同一集合, 则可以合并, 否则加入(u, v)这条边就会形成一个环。
假设有3条路: (1, 2), (2, 3), (3, 1)
遍历到第三条边的时候就会发现, 节点3和1指向同一个父亲, 形成2->1->3->2的环
"""
if rootu != rootv:
p[rootu] = rootv
ans = max(ans, w)
# 最小生成树: 图的所有生成树中具有边上的权值之和最小的树称为图的最小生成树
# 改造后的道路就是一个最小生成树, 树的边数 = 节点数 - 1
print(n - 1, ans)
# 第二种: Prim算法
INF = 1e9
n, m = map(int, input().split())
Map = [[INF] * (n + 1) for _ in range(n + 1)]
for _ in range(m):
u, v, w = map(int, input().split())
# u和v之间可能有多条路, 取其中最短的
Map[u][v] = Map[v][u] = min(Map[u][v], w)
# 1.最开始的起始点
u = 1
# d[i]表示点i离集合的距离
d = [INF] * (n + 1)
# 记录最大的那条路的分值
ans = 0
# 2.不断利用起始点u更新d数组, 进行n - 1次循环
for i in range(n - 1):
# 起始点u到集和距离为0
d[u] = 0
# 2.1.利用u更新d, 同时找出d最小的未加入集合的点
next_u, next_val = 1, INF
for v in range(1, n + 1):
# d[v]==0表示点已经在集合中了, 所以与集合的距离为0
if d[v] == 0:
continue
# 更新节点v与集合(或者起始点u)的距离
d[v] = min(d[v], Map[u][v])
# 找出未加入集合中与集合最近的点
if d[v] < next_val:
# 下一轮循环的起始点u
next_u = v
# 不断更新其他点与集合的最小距离
next_val = d[v]
ans = max(ans, next_val)
u = next_u
# 最小生成树: 图的所有生成树中具有边上的权值之和最小的树称为图的最小生成树
# 改造后的道路就是一个最小生成树, 树的边数 = 节点数 - 1
print(n - 1, ans)