#include <bits/stdc++.h>
using namespace std;
template <typename T>
ostream& operator<<(ostream& os, const vector<T>& v) {
  os << "[";
  for (int i = 0; i < v.size(); ++i) {
    os << v[i];
    if (i != v.size() - 1) os << ", ";
  }
  os << "]";
  return os;
}
template <typename T>
void print(T v, string s = "\n") {
  cout << v << s;
}
template <typename T>
void in(T& v) {
  cin >> v;
}
class DisjointSet {
 public:
  DisjointSet(long long n) {
    rank.resize(n);
    parent.resize(n);
    sizes.resize(n);
    for (long long i = 0; i < n; i++) rank[i] = 0, parent[i] = i, sizes[i] = 1;
  }
  bool same(long long x, long long y) { return root(x) == root(y); }
  void unite(long long x, long long y) {
    x = root(x), y = root(y);
    if (x == y) return;
    if (rank[x] < rank[y])
      swap(x, y);
    else if (rank[x] == rank[y])
      rank[x]++;
    parent[y] = x;
    sizes.at(x) += sizes.at(y);
  }
  long long size(long long x) { return sizes.at(root(x)); }

 private:
  vector<long long> rank;
  vector<long long> parent;
  vector<long long> sizes;
  long long root(long long x) {
    if (x != parent[x]) {
      parent[x] = root(parent[x]);
    }
    return parent[x];
  }
};
int main() {
  int n, m;
  in(n), in(m);
  vector<tuple<int, int, int> > G;
  for (int i = 0; i < m; i++) {
    int a, b, c;
    in(a), in(b), in(c);
    a--;
    b--;
    G.emplace_back(c, a, b);
  }
  sort(G.begin(), G.end());
  DisjointSet dset(n);
  int cnt = 0;
  for (auto&& edge : G) {
    int c, a, b;
    tie(c, a, b) = edge;
    if (!dset.same(a, b)) {
      cnt++;
      dset.unite(a, b);
    }
  }
  print(m - cnt);
  return 0;
}
