#if 1
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <stack>
#include <array>
#include <deque>
#include <algorithm>
#include <utility>
#include <cstdint>
#include <functional>
#include <iomanip>
#include <numeric>
#include <assert.h>
#include <bitset>

auto& in = std::cin;
auto& out = std::cout;

#include <vector>

class union_find
{
private:
	std::vector<int>parent;
	std::vector<int>rank;
public:
	union_find(int siz)
	{
		parent.resize(siz);
		rank.resize(siz);
		for (int i = 0; i < siz; ++i)
		{
			parent[i] = i;
			rank[i] = 0;
		}
	}
	void unite(int x, int y)
	{
		int xr = find(x);
		int yr = find(y);
		if (rank[xr] > rank[yr])
		{
			parent[yr] = xr;
		}
		else if (rank[xr] > rank[yr])
		{
			parent[xr] = yr;
		}
		else if (xr != yr)
		{
			parent[yr] = xr;
			++rank[xr];
		}
	}
	int find(int x)
	{
		if (parent[x] == x)
		{
			return x;
		}
		else
		{
			return parent[x] = find(parent[x]);
		}
	}
	bool check(int x, int y)
	{
		return find(x) == find(y);
	}
};

constexpr int32_t MAX_N = 50;//頂点数
constexpr int32_t MAX_LOGN = 6;//log2頂点数
std::vector<int32_t> graph[MAX_N];//木
std::vector<int32_t> graph_num[MAX_N];//木
int32_t parent[MAX_N][MAX_LOGN] = {}; // 親ノード(根ノードの親は-1とする)
int32_t depth[MAX_N]; // 根からの深さ
void init_parent(int32_t v = 0, int32_t p = 0, int32_t d = 0) {
	parent[v][0] = p;
	for (int32_t i = 0; parent[parent[v][i]][i] != 0; ++i) {
		parent[v][i + 1] = parent[parent[v][i]][i];
	}
	depth[v] = d;
	for (auto& c : graph[v]) {
		if (c != p) init_parent(c, v, d + 1);
	}
}
int32_t get_LCA(int32_t v1, int32_t v2)
{
	if (depth[v1] > depth[v2]) {
		std::swap(v1, v2);
	}
	//高さをそろえる
	{
		int32_t dis = depth[v2] - depth[v1];
		int32_t tmp = 0;
		while (dis) {
			if (dis & 1) {
				v2 = parent[v2][tmp];
			}
			dis >>= 1;
			++tmp;
		}
	}
	//LCA
	if (v1 == v2) {
		return v1;
	}
	for (int32_t k = MAX_LOGN - 1; k >= 0; --k) {
		if (parent[v1][k] != parent[v2][k]) {
			v1 = parent[v1][k];
			v2 = parent[v2][k];
		}
	}
	return parent[v1][0];
}

int32_t N, M;
int32_t edge[50][2];
bool in_tree[50];//その辺が木に使われているときtrue
std::set<std::pair<int, int>> closed_pass;//木を構成する辺の内、閉路に使われている辺
int main()
{
	using std::endl;
	in.sync_with_stdio(false);
	out.sync_with_stdio(false);
	in.tie(nullptr);
	out.tie(nullptr);

	in >> N>>M;
	for (int32_t i = 0; i < M; i++)
	{
		in >> edge[i][0] >> edge[i][1];
		--edge[i][0]; --edge[i][1];
	}

	//方針：
	//1.木を作る
	//2.木に辺を足す=>閉路が出来るなので、新しくできた閉路の辺にカウントをする
	//3.(N-1)-(木の内閉路を構成する辺の数)が答え

	//木を作る
	{
		union_find uf(N);
		for (int32_t i = 0; i < M; i++)
		{
			if (uf.check(edge[i][0], edge[i][1]) == false)
			{
				uf.unite(edge[i][0], edge[i][1]);
				in_tree[i] = true;
				graph[edge[i][0]].push_back(edge[i][1]);
				graph[edge[i][1]].push_back(edge[i][0]);
				graph_num[edge[i][0]].push_back(i);
				graph_num[edge[i][1]].push_back(i);
			}
		}
	}
	init_parent(0);

	for (int32_t i = 0; i < M; i++)
	{
		if (in_tree[i]) { continue; }

		auto add_closed_pass = [](int v1, int v2) {
			if (v2 < v1) { std::swap(v1, v2); }
			closed_pass.emplace(v1, v2);
		};
		auto lca = get_LCA(edge[i][0], edge[i][1]);
		int v = edge[i][0];
		while (v != lca) {
			int p = parent[v][0];//一つ上の親
			add_closed_pass(v, p);
			v = p;
		}
		v = edge[i][1];
		while (v != lca) {
			int p = parent[v][0];//一つ上の親
			add_closed_pass(v, p);
			v = p;
		}
	}

	out << (N - 1) - ((int)closed_pass.size()) << endl;

	return 0;
}
#endif


Measured memory_bytes: 256