#include <bits/stdc++.h>
using namespace std;
using namespace std;
static const long long INF = 1e15;
struct edge {
  int from;
  int to;
  long long cost;
  edge(int from, int to, long long cost) : from(from), to(to), cost(cost) {}
};
struct UnionFind {
 public:
  vector<long long> par;
  vector<long long> siz;
  UnionFind(long long sz_) : par(sz_), siz(sz_, 1LL) {
    for (long long i = 0; i < sz_; ++i) par[i] = i;
  }
  void init(long long sz_) {
    par.resize(sz_);
    siz.assign(sz_, 1LL);
    for (long long i = 0; i < sz_; ++i) par[i] = i;
  }
  long long root(long long x) {
    while (par[x] != x) {
      x = par[x] = par[par[x]];
    }
    return x;
  }
  bool merge(long long x, long long 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 issame(long long x, long long y) { return root(x) == root(y); }
  long long size(long long x) { return siz[root(x)]; }
};
struct KruskalRunner {
  static bool orderByCost(const edge& e1, const edge& e2) {
    return e1.cost < e2.cost;
  }
  vector<int> solve(int vCount, vector<edge>& edges) {
    sort(edges.begin(), edges.end(), orderByCost);
    UnionFind uf = UnionFind(vCount);
    vector<int> used;
    for (int i = 0; i < edges.size(); ++i) {
      edge e = edges[i];
      if (!uf.issame(e.from, e.to)) {
        uf.merge(e.from, e.to);
        used.push_back(i);
      }
    }
    return used;
  }
};
int main() {
  long long n, m;
  cin >> n >> m;
  vector<edge> es;
  for (long long i = 0; i < m; i++) {
    int a, b;
    long long c;
    cin >> a >> b >> c;
    a--;
    b--;
    es.emplace_back(a, b, c);
  }
  KruskalRunner r;
  auto c = r.solve(n, es);
  cout << m - c.size() << endl;
  return 0;
}
