Tree is a connected undirected graph that has no cycles. Edge cactus is a connected undirected graph without loops and parallel edges, such that each edge belongs to at most one cycle.

Vasya has an edge cactus, each edge of this graph has some color.

Vasya would like to remove the minimal number of edges in such way that his cactus turned to a tree. Vasya wants to make it in such a way that there were edges of as many different colors in the resulting tree, as possible. Help him to find how many different colors can the resulting tree have.

Input

The first line contains two integers: n, m (2 ≤ n ≤ 10 000) — the number of vertices and the number of edges in Vasya's graph, respectively.

The following m lines contain three integers each: u, v, c (1 ≤ u, v ≤ n, u ≠ v, 1 ≤ c ≤ m) — the numbers of vertices connected by the corresponding edge, and its color. It is guaranteed that the described graph is indeed an edge cactus.

Output

Output one integer: the maximal number of different colors that the resulting tree can have.

Examples

Input

4 4
1 2 4
2 3 1
3 4 2
4 2 3


Output

3


Input

7 9
1 2 1
2 3 4
3 1 5
1 4 5
4 5 2
5 1 6
1 6 4
6 7 6
7 1 3


Output

6
#include <bits/stdc++.h>
using namespace std;
const long long int inf = 1000000000;
const long long int mod = 1000000000 + 7;
inline void IO() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
}
inline int dcmp(long double x) { return x < -1e-12 ? -1 : (x > 1e-12); }
template <class T>
inline int CHECK(T MASK, int i) {
  return (MASK >> i) & 1;
}
template <class T>
inline T ON(T MASK, int i) {
  return MASK | (T(1) << i);
}
template <class T>
inline T OFF(T MASK, int i) {
  return MASK & (~(T(1) << i));
}
template <typename T>
inline int CNT(T MASK) {
  if (numeric_limits<T>::digits <= numeric_limits<unsigned int>::digits)
    return __builtin_popcount(MASK);
  else
    return __builtin_popcountll(MASK);
}
template <class T>
inline int RIGHT(T MASK) {
  return log2(MASK & -MASK);
}
int dx4[] = {0, 0, -1, +1};
int dy4[] = {+1, -1, 0, 0};
int dx8[] = {1, 1, 0, -1, -1, -1, 0, 1, 0};
int dy8[] = {0, 1, 1, 1, 0, -1, -1, -1, 0};
inline void I(int& a) { scanf("%d", &a); }
inline void I(long long int& a) { scanf("%I64d", &a); }
inline void I(unsigned long long int& a) { scanf("%I64u", &a); }
inline void I(char* a) { scanf("%s", a); }
char Iarr[2000010];
inline void I(string& a) {
  scanf("%s", Iarr);
  a = Iarr;
}
template <typename T, typename... Args>
void I(T& a, Args&... args) {
  I(a);
  I(args...);
}
inline void OUT(int a) { printf("%d", a); }
inline void OUT(long long int a) { printf("%I64d", a); }
inline void OUT(const char* a) { printf("%s", a); }
inline void OUT(char* a) { printf("%s", a); }
inline void OUT(unsigned long long int a) { printf("%I64u", a); }
template <typename T, typename... Args>
void OUT(T a, Args... args) {
  OUT(a);
  OUT(" ");
  OUT(args...);
}
template <typename... Args>
void O(Args... args) {
  OUT(args...);
  OUT("\n");
}
template <typename T1, typename T2>
inline ostream& operator<<(ostream& os, pair<T1, T2> p) {
  os << "{" << p.first << ", " << p.second << "}";
  return os;
}
template <typename T>
inline ostream& operator<<(ostream& os, vector<T>& a) {
  os << "[";
  for (int i = 0; i < (int)a.size(); i++) {
    if (i) os << ", ";
    os << a[i];
  }
  os << "]";
  return os;
}
void err(istream_iterator<string> it) {}
template <typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
  cout << *it << " = " << a << endl;
  err(++it, args...);
}
const int M = 200010;
struct graph {
  int n;
  vector<vector<int>> adj;
  graph(int n) : n(n), adj(n) {}
  void add_edge(int u, int v) {
    adj[u].push_back(v);
    adj[v].push_back(u);
  }
  int add_node() {
    adj.push_back({});
    return n++;
  }
  vector<int>& operator[](int u) { return adj[u]; }
};
pair<vector<vector<int>>, vector<int>>
biconnected_components_articulation_points(graph& adj) {
  int n = adj.n;
  vector<int> num(n), low(n), art(n), stk;
  vector<vector<int>> comps;
  function<void(int, int, int&)> dfs = [&](int u, int p, int& t) {
    num[u] = low[u] = ++t;
    stk.push_back(u);
    for (int v : adj[u])
      if (v != p) {
        if (!num[v]) {
          dfs(v, u, t);
          low[u] = min(low[u], low[v]);
          if (low[v] >= num[u]) {
            art[u] = (num[u] > 1 || num[v] > 2);
            comps.push_back({u});
            while (comps.back().back() != v)
              comps.back().push_back(stk.back()), stk.pop_back();
          }
        } else
          low[u] = min(low[u], num[v]);
      }
  };
  for (int u = 0, t; u < n; ++u)
    if (!num[u]) dfs(u, -1, t = 0);
  return {comps, art};
}
graph build_block_cut_tree(graph& adj) {
  int n = adj.n;
  auto pre = biconnected_components_articulation_points(adj);
  vector<int> art = pre.second;
  vector<vector<int>> comps = pre.first;
  graph tree(0);
  vector<int> id(n);
  for (int u = 0; u < n; ++u)
    if (art[u]) id[u] = tree.add_node();
  for (auto& comp : comps) {
    int node = tree.add_node();
    for (int u : comp)
      if (!art[u])
        id[u] = node;
      else
        tree.add_edge(node, id[u]);
  }
  return tree;
};
struct PushRelabel {
  struct Edge {
    int dest, back;
    long long f, c;
  };
  vector<vector<Edge>> g;
  vector<long long> ec;
  vector<Edge*> cur;
  vector<vector<int>> hs;
  vector<int> H;
  PushRelabel(int n) : g(n), ec(n), cur(n), hs(2 * n), H(n) {}
  void addEdge(int s, int t, long long cap, long long rcap = 0) {
    if (s == t) return;
    g[s].push_back({t, (int)g[t].size(), 0, cap});
    g[t].push_back({s, (int)g[s].size() - 1, 0, rcap});
  }
  void addFlow(Edge& e, long long f) {
    Edge& back = g[e.dest][e.back];
    if (!ec[e.dest] && f) hs[H[e.dest]].push_back(e.dest);
    e.f += f;
    e.c -= f;
    ec[e.dest] += f;
    back.f -= f;
    back.c += f;
    ec[back.dest] -= f;
  }
  long long calc(int s, int t) {
    int v = (int)g.size();
    H[s] = v;
    ec[t] = 1;
    vector<int> co(2 * v);
    co[0] = v - 1;
    for (int i = 0; i < (v); ++i) cur[i] = g[i].data();
    for (auto& e : g[s]) addFlow(e, e.c);
    for (int hi = 0;;) {
      while (hs[hi].empty())
        if (!hi--) return -ec[s];
      int u = hs[hi].back();
      hs[hi].pop_back();
      while (ec[u] > 0)
        if (cur[u] == g[u].data() + (int)(g[u]).size()) {
          H[u] = 1e9;
          for (auto& e : g[u])
            if (e.c && H[u] > H[e.dest] + 1) H[u] = H[e.dest] + 1, cur[u] = &e;
          if (++co[H[u]], !--co[hi] && hi < v)
            for (int i = 0; i < (v); ++i)
              if (hi < H[i] && H[i] < v) --co[H[i]], H[i] = v + 1;
          hi = H[u];
        } else if (cur[u]->c && H[u] == H[cur[u]->dest] + 1)
          addFlow(*cur[u], min(ec[u], cur[u]->c));
        else
          ++cur[u];
    }
  }
  bool leftOfMinCut(int a) { return H[a] >= (int)(g).size(); }
};
int main() {
  int n, m;
  I(n, m);
  graph g(n);
  unordered_map<int, int> unordered_map[n];
  for (int i = 0; i < m; i++) {
    int u, v, c;
    I(u, v, c);
    u--;
    v--;
    g.add_edge(u, v);
    unordered_map[u][v] = c;
  }
  auto pre = biconnected_components_articulation_points(g);
  int nn = (int)pre.first.size() + m + 2;
  PushRelabel D(nn);
  int cnt = 0;
  for (auto comp : pre.first) {
    set<int> st;
    for (int x : comp) {
      st.insert(x);
    }
    for (int x : comp) {
      for (int y : g[x]) {
        if (st.find(y) != st.end()) {
          if (unordered_map[x].find(y) != unordered_map[x].end()) {
            D.addEdge(cnt, (int)pre.first.size() - 1 + unordered_map[x][y], 1);
          }
        }
      }
    }
    D.addEdge(nn - 2, cnt, (int)comp.size() - 1);
    cnt++;
  }
  for (int i = 1; i <= m; i++) {
    D.addEdge((int)pre.first.size() - 1 + i, nn - 1, 1);
  }
  O(D.calc(nn - 2, nn - 1));
  return 0;
}
