// Slower C++ solution:
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for(int i=0; i<(n); i++)

using ll = long long;

#define int long long

using P = pair<int,int>;

vector<int> graph[110000];

int visited[110000];



void dfs(int now,int last = -1){

    for(auto next:graph[now]){

        if(next == last)continue;

        if(visited[next])continue;

        visited[next] = 1;

        dfs(next,now);

    }

}

signed main(){

    

    int n,m;cin >> n >> m;

    rep(i,m){

        int a,b,c;

        cin >> a >> b >> c;

        a--;b--;

        graph[a].push_back(b);

        graph[b].push_back(a);

    }

    int ans = 0;

    rep(i,n){

        if(!visited[i]){

            dfs(i);

            ans++;

        }

    }

    cout << ans << endl;

    return 0;

}

// Faster C++ solution for the same problem (measured speedup: 3.631724392100469x):
#include <bits/stdc++.h>

#define LOCAL

using namespace std;

template <typename A, typename B>

ostream& operator <<(ostream& out, const pair<A, B>& a) {

out << "(" << a.first << "," << a.second << ")";

return out;

}

template <typename T, size_t N>

ostream& operator <<(ostream& out, const array<T, N>& a) {

out << "["; bool first = true;

for (auto& v : a) { out << (first ? "" : ", "); out << v; first = 0;} out << "]";

return out;

}

template <typename T>

ostream& operator <<(ostream& out, const vector<T>& a) {

out << "["; bool first = true;

for (auto& v : a) { out << (first ? "" : ", "); out << v; first = 0;} out << "]";

return out;

}

template <typename T, class Cmp>

ostream& operator <<(ostream& out, const set<T, Cmp>& a) {

out << "{"; bool first = true;

for (auto& v : a) { out << (first ? "" : ", "); out << v; first = 0;} out << "}";

return out;

}

template <typename U, typename T, class Cmp>

ostream& operator <<(ostream& out, const map<U, T, Cmp>& a) {

out << "{"; bool first = true;

for (auto& p : a) { out << (first ? "" : ", "); out << p.first << ":" << p.second; first = 0;} out << "}";

return out;

}

#ifdef LOCAL

#define trace(...) __f(#__VA_ARGS__, __VA_ARGS__)

#else

#define trace(...) 42

#endif

template <typename Arg1>

void __f(const char* name, Arg1&& arg1){

cerr << name << ": " << arg1 << endl;

}

template <typename Arg1, typename... Args>

void __f(const char* names, Arg1&& arg1, Args&&... args){

const char* comma = strchr(names + 1, ',');

cerr.write(names, comma - names) << ": " << arg1 << " |";

__f(comma + 1, args...);

}

#define rep(i,n) for(int i=0; i<(n); i++)

using ll = long long;

#define int long long

using P = pair<int,int>;

//#########################################

struct UnionFind {

    vector<int> d;

    UnionFind(int n=0): d(n,-1) {}

    int root(int x) {

      if (d[x] < 0) return x;

      return d[x] = root(d[x]);

    }

    bool unite(int x, int y) {

      x = root(x); y = root(y);

      if (x == y) return false;

      if (d[x] > d[y]) swap(x,y);

      d[x] += d[y];

      d[y] = x;

      return true;

    }

    bool same(int x, int y) { return root(x) == root(y);}

    int size(int x) { return -d[root(x)];}

  };

signed main(){

    int n,m;cin >> n >> m;

    UnionFind uf(n);

    rep(i,m){

        int a,b,c;cin >> a >> b >> c;

        a--;b--;

        uf.unite(a,b);

    }

    int ret = 0;

    vector<int> vis(n);

    rep(i,n){

        int x = uf.root(i);

        if(vis[x])continue;

        vis[x] = 1;

        ret++;



    }

    cout << ret << endl;



    return 0;

}