#include <bits/stdc++.h>
using namespace std;
const int N = 30010;
struct Dinic {
  struct edge {
    int u, v, c, f;
    edge(int u = 0, int v = 0, int c = 0, int f = 0) : u(u), v(v), c(c), f(f) {}
  };
  vector<vector<int> > G;
  vector<edge> e;
  vector<int> d, ptr;
  int n, src, snk;
  static const int INF = 1e9 + 100;
  Dinic(int n = 0, int src = 0, int snk = 0) : n(n), src(src), snk(snk) {
    G.assign(n + 1, vector<int>());
    e.clear();
  }
  void addEdge(int u, int v, int c) {
    G[u].push_back(e.size());
    e.push_back(edge(u, v, c, 0));
    G[v].push_back(e.size());
    e.push_back(edge(v, u, 0, 0));
  }
  bool bfs() {
    d.assign(n + 1, 0);
    ptr.assign(n + 1, 0);
    for (int i = 1; i <= n; i++) d[i] = INF;
    d[src] = 0;
    queue<int> q;
    q.push(src);
    while (!q.empty()) {
      int u = q.front();
      q.pop();
      for (int id : G[u])
        if (d[e[id].v] == INF && e[id].f < e[id].c)
          q.push(e[id].v), d[e[id].v] = d[u] + 1;
    }
    return (d[snk] != INF);
  }
  int dfs(int u, int flow) {
    if (!flow || u == snk) return flow;
    for (int &i = ptr[u]; i < G[u].size(); i++) {
      int id = G[u][i], v = e[id].v;
      if (d[v] != d[u] + 1 || e[id].c <= e[id].f) continue;
      if (int nxt = dfs(v, min(flow, e[id].c - e[id].f))) {
        e[id].f += nxt;
        e[id ^ 1].f -= nxt;
        return nxt;
      }
    }
    return 0;
  }
  int maxFlow() {
    int res = 0;
    while (bfs())
      while (int flow = dfs(src, INF)) res += flow;
    return res;
  }
} dn;
int n, m, low[N], num[N], step, tot;
vector<pair<int, int> > adj[N];
bool isBridge[N], block[N], inStack[N];
int color[N], cnt[N], cnt1;
stack<int> s;
vector<int> q;
map<pair<int, int>, int> save;
int src, snk;
void dfs(int u, int p) {
  low[u] = num[u] = ++step;
  for (pair<int, int> cur : adj[u]) {
    int v = cur.first;
    if (v == p) continue;
    if (num[v])
      low[u] = min(low[u], num[v]);
    else {
      dfs(v, u), low[u] = min(low[u], low[v]);
      if (low[v] == num[v])
        isBridge[cur.second] = 1, ++cnt1, dn.addEdge(src, cnt1, 1),
        dn.addEdge(cnt1, n + color[cur.second], 1);
    }
  }
}
bool check[N];
void findCycle(int u) {
  if (inStack[u]) {
    q.clear();
    int v = 0, cur = u, num = 0;
    do {
      v = s.top();
      inStack[v] = 0;
      s.pop();
      int c = color[save[pair<int, int>(cur, v)]];
      cnt[c]++;
      q.push_back(c);
      cur = v;
      ++num;
    } while (v != u);
    cnt1++;
    for (int c : q)
      if (!check[c]) check[c] = 1, dn.addEdge(cnt1, n + c, cnt[c]);
    dn.addEdge(src, cnt1, num - 1);
    for (int c : q) cnt[c] = check[c] = 0;
  }
  s.push(u);
  inStack[u] = 1;
  while (!adj[u].empty() &&
         (isBridge[adj[u].back().second] || block[adj[u].back().second]))
    adj[u].pop_back();
  if (adj[u].empty()) {
    s.pop();
    return;
  } else {
    int v = adj[u].back().first, id = adj[u].back().second;
    block[id] = 1;
    return findCycle(v);
  }
}
int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(0);
  cin >> n >> m;
  tot = n + m;
  src = ++tot;
  snk = ++tot;
  dn = Dinic(tot, src, snk);
  for (int i = 1; i <= m; i++) {
    int u, v, c;
    cin >> u >> v >> c;
    adj[u].push_back(pair<int, int>(v, i));
    adj[v].push_back(pair<int, int>(u, i));
    save[pair<int, int>(u, v)] = save[pair<int, int>(v, u)] = i;
    color[i] = c;
  }
  dfs(1, 1);
  for (int i = 1; i <= n; i++) findCycle(i);
  for (int i = 1; i <= m; i++) dn.addEdge(n + i, snk, 1);
  cout << dn.maxFlow() << endl;
}
