#include <algorithm>
#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <typeinfo>
#include <utility>
#include <valarray>
#include <vector>
#include <climits>
#include <cstring>
#include <cassert>

#define rep(i, m, n) for(int i=int(m);i<int(n);i++)
#define all(c) begin(c),end(c)

template<typename T1, typename T2>
inline void chmin(T1 &a, T2 b) { if (a > b) a = b; }

template<typename T1, typename T2>
inline void chmax(T1 &a, T2 b) { if (a < b) a = b; }

typedef long long int ll;
using ll = long long int;
using ull = long long unsigned int;
using Int = long long int;
using uInt = long long unsigned int;
using Double = long double;
using namespace std;
#define INF (1 << 30) - 1
#define INFl (ll)5e15
#define DEBUG 0
#define dump(x)  cerr << #x << " = " << (x) << endl
#define MOD 1000000007


//edit
class UnionFind {
    vector<int> p;//p[i]はiの属する組織
    vector<int> sz;

public:
    UnionFind(int n) {
        p.resize(n);
        sz.resize(n);

        for (int i = 0; i < n; i++) {
            p[i] = i;
            sz[i] = 1;
        }
    }

/* xの属する集合を返す */
    int find(int x) {
        if (p[x] == x) return x;
        return p[x] = find(p[x]);
    }

/* yにxを統合する */
    void unite(int x, int y) {
        sz[find(y)] += sz[find(x)];
        sz[find(x)] = 0;
        p[find(x)] = p[find(y)];
    }

/* xとyが属する集合が同じかを判定する */
    bool same(int x, int y) {
        return find(x) == find(y);
    }

/* xに属する集合のサイズを求める */
    int size(int x) {
        return sz[find(x)];
    }
};

struct edge {
    Int from, to, cost;

    edge &operator=(const edge &e) {
        from = e.from;
        to = e.to;
        cost = e.cost;
        return *this;
    }

    bool operator==(const edge &right) const {
        return from == right.from &&
               to == right.to &&
               cost == right.cost;
    }
};

class Solve {
public:
    Int N, M;
    vector<edge> edges;

    pair<Int, vector<edge>> make_mst() {
        UnionFind uf(N);
        Int ret_sum = 0;
        vector<edge> ret_e;

        for (auto e : edges) {
            Int a = e.from;
            Int b = e.to;
            if (!uf.same(a, b)) {
                uf.unite(a, b);
                ret_e.push_back(e);
                ret_sum += e.cost;
            }
        }

        return make_pair(ret_sum, ret_e);
    }

    void solve() {
        cin >> N >> M;
        for (int i = 0; i < M; ++i) {
            Int a, b, c;
            cin >> a >> b >> c;
            a--, b--;
            edges.push_back(edge{a, b, c});
        }
        sort(all(edges), [](auto el, auto er) {
            return el.cost < er.cost;
        });

        Int mst_cost;
        vector<edge> mst;

        tie(mst_cost, mst) = make_mst();

        Int cnt = 0, sum = 0;

        for (auto e : mst) {
            // e が存在しない場合のmstを求める
            Int sum_cost = 0;
            UnionFind uf(N);
            for (auto f : edges) {
                if (f == e) continue;
                if (!uf.same(f.from, f.to)) {
                    uf.unite(f.from, f.to);
                    sum_cost += f.cost;
                }
            }
            if (uf.size(0) != N || sum_cost > mst_cost) {
                cnt++;
                sum += e.cost;
            }
        }

        cout << cnt << " " << sum << endl;


    }
};


int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(10);
//    std::ifstream in("input.txt");
//    std::cin.rdbuf(in.rdbuf());

    Solve().solve();


    return 0;
}



Measured memory_bytes: 4568