#include <bits/stdc++.h>
using namespace std;
const int vx[4] = {0, 1, 0, -1};
const int vy[4] = {1, 0, -1, 0};
template <typename T>
struct edge {
  long long src, to;
  T cost;
  bool used = false;
  edge(long long to, T cost) : src(-1), to(to), cost(cost) {}
  edge(long long src, long long to, T cost) : src(src), to(to), cost(cost) {}
  edge &operator=(const long long &x) {
    to = x;
    return *this;
  }
  operator long long() const { return to; }
};
template <typename T>
using Edges = vector<edge<T> >;
template <typename T>
using WG = vector<Edges<T> >;
using UG = vector<vector<long long> >;
template <typename T>
using Matrix = vector<vector<T> >;
struct UnionFind {
  vector<long long> par;
  vector<long long> sizes;
  UnionFind(long long n) : par(n), sizes(n, 1) {
    for (long long i = (long long)0; i < (long long)n; i++) par[i] = i;
  }
  long long find(long long x) {
    if (x == par[x]) return x;
    return par[x] = find(par[x]);
  }
  bool unite(long long x, long long y) {
    x = find(x);
    y = find(y);
    if (x == y) return false;
    if (sizes[x] < sizes[y]) swap(x, y);
    par[y] = x;
    sizes[x] += sizes[y];
    return true;
  }
  long long size(long long x) { return sizes[find(x)]; }
};
template <typename T>
T kruskal(Edges<T> &es, long long n) {
  sort(es.begin(), es.end(),
       [](const edge<T> &a, const edge<T> &b) { return (a.cost < b.cost); });
  UnionFind uf(n);
  T min_cost = 0;
  for (auto &e : es) {
    if (uf.unite(e.src, e.to)) {
      min_cost += e.cost;
      e.used = true;
    }
  }
  return min_cost;
}
signed main() {
  cin.tie(0);
  ios::sync_with_stdio(false);
  cout << fixed << setprecision(20);
  long long n, m;
  cin >> n >> m;
  Edges<long long> es;
  for (long long i = (long long)0; i < (long long)m; i++) {
    long long a, b, c;
    cin >> a >> b >> c;
    a--;
    b--;
    es.push_back(edge<long long>(a, b, c));
  }
  kruskal(es, n);
  long long ans = 0;
  for (auto e : es) {
    ans += !e.used;
  }
  cout << ans << endl;
}
