#include <bits/stdc++.h>
template <class T>
inline T chmax(T& a, const T b) {
  return a = (a < b) ? b : a;
}
template <class T>
inline T chmin(T& a, const T b) {
  return a = (a > b) ? b : a;
}
using ll = long long;
using ull = unsigned long long;
using ld = long double;
const ll MOD = 1000000007;
const ll INF = 1e18;
const double PI = acos(-1);
using namespace std;
class UnionFind {
 public:
  vector<ll> par;
  vector<ll> siz;
  UnionFind(ll sz_) : par(sz_), siz(sz_, 1LL) {
    for (ll i = 0; i < sz_; ++i) par[i] = i;
  }
  void init(ll sz_) {
    par.resize(sz_);
    siz.assign(sz_, 1LL);
    for (ll i = 0; i < sz_; ++i) par[i] = i;
  }
  ll root(ll x) {
    while (par[x] != x) {
      x = par[x] = par[par[x]];
    }
    return x;
  }
  bool unite(ll x, ll y) {
    x = root(x);
    y = root(y);
    if (x == y) return false;
    if (siz[x] < siz[y]) swap(x, y);
    siz[x] += siz[y];
    par[y] = x;
    return true;
  }
  bool same(ll x, ll y) { return root(x) == root(y); }
  ll size(ll x) { return siz[root(x)]; }
};
template <typename T>
T kruskal(vector<vector<pair<ll, ll>>>& graph, ll N) {
  priority_queue<pair<ll, pair<ll, ll>>, vector<pair<ll, pair<ll, ll>>>,
                 greater<pair<ll, pair<ll, ll>>>>
      q;
  for (ll i = 0; i < ll(N); ++i)
    for (ll j = 0; j < ll(graph[i].size()); ++j) {
      q.push(make_pair(graph[i][j].second, make_pair(i, graph[i][j].first)));
    }
  UnionFind uf(N);
  T res = 0;
  while (!q.empty()) {
    auto p = q.top();
    q.pop();
    if (uf.unite(p.second.first, p.second.second)) {
      res++;
    }
  }
  return res;
}
signed main() {
  ll N, M;
  cin >> N >> M;
  vector<vector<pair<ll, ll>>> graph(N);
  for (ll i = 0; i < ll(M); ++i) {
    ll u, v, d;
    cin >> u >> v >> d;
    u--;
    v--;
    graph[u].emplace_back(v, d);
    graph[v].emplace_back(u, d);
  }
  cout << M - kruskal<ll>(graph, N) << endl;
  return 0;
}
