#include <iostream>
#include <vector>
#include <set>
#include <queue>
using namespace std;

class UnionFind {
private:
	int num_unions;
	vector<int> par;
	vector<int> rank;
public:
	UnionFind(int N);
	int root(int node);
	void marge(int node_a, int node_b);
	int count_unions();
};

int main() {
	int N, M;
	cin >> N >> M;

	UnionFind uf(N);
	for (int i = 0; i < M; i++) {
		int X, Y, Z;
		cin >> X >> Y >> Z;
		uf.marge(X - 1, Y - 1);
	}
	cout << uf.count_unions() << endl;
}

//各ノードの初期値はそれ自身（自身がノードの根）
UnionFind::UnionFind(int N) : par(N), num_unions(N),rank(N,0) {
	for (int i = 0; i < N; i++) {
		par[i] = i;
	}
}

int UnionFind::root(int node) {
	if (par[node] == node) return node;
	else {
		return par[node] = root(par[node]);//経路圧縮
	}
}

void UnionFind::marge(int node_a, int node_b) {
	int root_a = root(node_a);
	int root_b = root(node_b);
	if (root_a == root_b) return;
	else {
		if (rank[root_a] < rank[root_b]) {
			par[root_a] = root_b;
		}
		else {
			par[root_b] = root_a;
			if (rank[root_a] == rank[root_b]) rank[root_a]++;
		}
	} 
}

int UnionFind::count_unions() {
	int ret = 0;
	for (int i = 0; i < num_unions; i++) {
		if (par[i] == i) ret++;
	}
	return ret;
}

//TLE 一番簡素なUnionFind
//class UnionFind {
//private:
//	int num_unions;
//	vector<int> par;
//public:
//	UnionFind(int N);
//	int root(int node);
//	void marge(int node_a, int node_b);
//	int count_unions();
//};
//
//int main() {
//	int N, M;
//	cin >> N >> M;
//	
//	UnionFind uf(N);
//	for (int i = 0; i < M; i++) {
//		int X, Y, Z;
//		cin >> X >> Y >> Z;
//		uf.marge(X-1, Y-1);
//	}
//	cout << uf.count_unions() << endl;
//}
//
////各ノードの初期値はそれ自身（自身がノードの根）
//UnionFind::UnionFind(int N) : par(N), num_unions(N) {
//	for (int i = 0; i < N; i++) {
//		par[i] = i;
//	}
//}
//
//int UnionFind::root(int node) {
//	if (par[node] == node) return node;
//	else {
//		return root(par[node]);
//	}
//}
//
//void UnionFind::marge(int node_a, int node_b) {
//	int root_a = root(node_a);
//	int root_b = root(node_b);
//	if (root_a == root_b) return;
//	else par[root_a] = root_b;
//}
//
//int UnionFind::count_unions() {
//	int ret = 0;
//	for (int i = 0; i < num_unions; i++) {
//		if (par[i] == i) ret++;
//	}
//	return ret;
//}

////BFS(while queue)による実装
//enum State {
//	UNDEFINED,
//	DEFINED,
//};
//int main() {
//	int N, M;
//		cin >> N >> M;
//		vector<set<int>> union_group(N);
//		for (int i = 0; i < M; i++) {
//			int X, Y, Z;
//			cin >> X >> Y >> Z;
//			union_group[X-1].insert(Y-1);
//			union_group[Y-1].insert(X-1);
//		}
//		vector<State> register_table(N, UNDEFINED);
//		queue<int> q_search_pos;
//
//		int ans = 0;
//
//		for (int i = 0; i < N; i++) {
//			if (register_table[i] == DEFINED) {
//				continue;
//			}
//			else {
//				ans++;
//				register_table[i] = DEFINED;
//				q_search_pos.push(i);
//				//BFS
//				while (q_search_pos.empty() != true) {
//					int search_pos = q_search_pos.front();
//					q_search_pos.pop();
//					for (auto itr = union_group[search_pos].begin(); itr != union_group[search_pos].end(); itr++) {
//						if (register_table[*itr] == DEFINED) {
//							continue;
//						}
//						else {
//							register_table[*itr] = DEFINED;
//							q_search_pos.push(*itr);
//						}
//					}
//				}
//			}
//		}
//		cout << ans << endl;
//		return 0;
//}

//AC DFSによる実装
//enum State {
//	UNDEFINED,
//	DEFINED,
//};
//
//void BFS(vector<set<int>>& u_g, vector<State>& r_t, int search_pos) {
//	for (auto itr = u_g[search_pos].begin(); itr != u_g[search_pos].end(); itr++) {
//		if (r_t[*itr] == DEFINED) {
//			continue;
//		}
//		else {
//			r_t[*itr] = DEFINED;
//			BFS(u_g, r_t, *itr);
//		}
//	}
//}
//
//int main() {
//	int N, M;
//	cin >> N >> M;
//	vector<set<int>> union_group(N);
//	for (int i = 0; i < M; i++) {
//		int X, Y, Z;
//		cin >> X >> Y >> Z;
//		union_group[X-1].insert(Y-1);
//		union_group[Y-1].insert(X-1);
//	}
//	vector<State> register_table(N, UNDEFINED);
//	
//	int ans = 0;
//
//	for (int i = 0; i < N; i++) {
//		if (register_table[i] == DEFINED) {
//			continue;
//		}
//		else {
//			ans++;
//			register_table[i] = DEFINED;
//		}
//		BFS(union_group, register_table, i);
//	}
//	cout << ans << endl;
//	return 0;
//}

//vectorの連結による解法　TLE
//void D_Show_c_d_i_g(const vector<vector<int>>& c_d_i_g) {
//	cout << "cards_divided_into_groups" << endl;
//	cout << "size:" << c_d_i_g.size() << endl;
//	for (int i = 0; i < c_d_i_g.size(); i++) {
//		for (int j = 0; j < c_d_i_g[i].size(); j++) {
//			cout << "c_d_i_g[" << i << "]" << c_d_i_g[i][j] << endl;
//		}
//	}
//	cout << endl;
//}
//void D_Show_g_t(const vector<int>&  g_t) {
//	cout << "group_table" << endl;
//	for (int i = 0; i < g_t.size(); i++) {
//		cout << "[" << i << "] " << g_t[i] << endl;
//	}
//	cout << endl;
//}
//
//int main() {
//	int N, M;
//	cin >> N >> M;
//	vector<int> group_table(N+1, -1);//用いるのは1~Nの要素 i番目のカードの属するグループNoを格納
//	vector<vector<int>> cards_divided_into_groups;
//	int group_counter = 0;
//
//	//以下のfor文でヒントの出ているものについてのグループ化が終了
//	for (int i = 0; i < M; i++) {
//		int X, Y, Z;
//		cin >> X >> Y >> Z; //X番目とY番目のカードの和がZ
//		//両方未登録
//		if (group_table[X] == -1 && group_table[Y] == -1) {
//			vector<int> cards_group;
//			group_table[X] = group_counter;
//			group_table[Y] = group_counter;
//			cards_group.push_back(X);
//			cards_group.push_back(Y);
//			cards_divided_into_groups.push_back(cards_group);
//			//cards_group.clear();
//			group_counter++;
//		}
//		//片方が登録済
//		else if (group_table[X] != -1 && group_table[Y] == -1) {
//			group_table[Y] = group_table[X];
//			cards_divided_into_groups[group_table[X]].push_back(Y);
//		}
//		else if (group_table[X] == -1 && group_table[Y] != -1) {
//			group_table[X] = group_table[Y];
//			cards_divided_into_groups[group_table[Y]].push_back(X);
//		}
//		//両方が登録済み
//		else{
//			int group_absorber, group_absorbed;
//			if (group_table[X] < group_table[Y]) {
//				group_absorber = group_table[X];
//				group_absorbed = group_table[Y];
//			}
//			else if (group_table[X] > group_table[Y]) {
//				group_absorber = group_table[Y];
//				group_absorbed = group_table[X];
//			}
//			else {
//				continue;
//			}
//			for (auto itr = cards_divided_into_groups[group_absorbed].begin(); itr != cards_divided_into_groups[group_absorbed].end(); itr++) {
//				cards_divided_into_groups[group_absorber].push_back(*itr);
//				group_table[*itr] = group_absorber;
//			}
//			cards_divided_into_groups.erase(cards_divided_into_groups.begin() + group_absorbed);
//			for (int i = 1; i <= N; i++) {
//				if (group_table[i] > group_absorbed) group_table[i]--;
//			}
//			group_counter--;
//		}
//	//	cout << "Result" << endl;
//	//	D_Show_g_t(group_table);
//	//	D_Show_c_d_i_g(cards_divided_into_groups);
//	}
//	//ヒントの出ていないもののカウント
//	unsigned int no_hint_count = 0;
//	for (int i = 1; i <= N; i++) {
//		if (group_table[i] == -1) no_hint_count++;
//	}
//	cout << cards_divided_into_groups.size() + no_hint_count << endl;
//	
//	return 0;
//}

Measured memory_bytes: 1024