import std.algorithm;
import std.container;
import std.conv;
import std.math;
import std.range;
import std.stdio;
import std.string;
import std.traits;


class UnionFind {
private:
  uint[] rank;
  uint[] par;
  uint size;

public:
  this(uint size) {
    rank = repeat(0u).take(size).array;
    par = iota(size).array;
    this.size = size;
  }

  uint length() {
    return size;
  }

  uint getRoot(uint n) {
    return par[n] = (par[n] == n ? n : getRoot(par[n]));
  }

  bool unite(uint n1, uint n2) {
    n1 = getRoot(n1);
    n2 = getRoot(n2);
    if (n1 == n2) return false;
    
    bool t = rank[n1] < rank[n2];
    par[t ? n1 : n2] = t ? n2 : n1;
    if (rank[n1] == rank[n2]) rank[n1]++;
    return true;
  }

  bool haveSameRoot(uint n1, uint n2) {
    return getRoot(n1) == getRoot(n2);
  }

  uint countRoots() {
    uint roots = 0;
    par.each!((i, v){ if (getRoot(v) == i) roots++; });
    return roots;
  }
}

unittest {
  auto uf = new UnionFind!5;
  uf.unite(0, 1);
  uf.unite(0, 2);
  uf.unite(3, 4);
  assert(uf.length == 5);
  assert(uf.haveSameRoot(1, 2));
  assert(uf.haveSameRoot(4, 3));
  assert(!uf.haveSameRoot(1, 4));
  assert(uf.countRoots == 2);
}


void main() {
  auto nm = readln.chomp.split.to!(uint[]);
  auto n = nm[0];
  auto m = nm[1];

  auto uf = new UnionFind(n);
  iota(m).each!((_){
    auto xyz = readln.chomp.split.to!(uint[]);
    uf.unite(xyz[0] - 1, xyz[1] - 1);
  });

  uf.countRoots.writeln;
}

