#include "bits/stdc++.h"
#include <sys/timeb.h>
#include <fstream>

using namespace std;

#define repr(i,a,b) for(int i=(int)(a);i<(int)(b);i++)
#define rep(i,n) repr(i,0,n)
#define reprrev(i,a,b) for(int i=(int)(b)-1;i>=(int)(a);i--)
#define reprev(i,n) reprrev(i,0,n)
#define repi(itr,ds) for(auto itr=ds.begin();itr!=ds.end();itr++)
#define all(a) (a).begin(),(a).end()
#define rall(a) (a).rbegin(),(a).rend()
#define mp make_pair
#define mt make_tuple
#define INF 1050000000
#define INFL 9e18
#define EPS (1e-10)
#define MOD 1000000007
#define PI 3.1415926536
#define RMAX 4294967295

typedef long long ll;
typedef pair<int, int> Pi;
typedef vector<int> vi;
typedef vector<ll> vll;
typedef vector<bool> vb;
typedef vector<char> vc;
typedef vector<string> vs;
typedef vector<double> vd;
typedef vector<Pi> vPi;
typedef vector<vector<int> > vvi;
typedef vector<vector<bool> > vvb;
typedef vector<vector<ll> > vvll;
typedef vector<vector<char> > vvc;
typedef vector<vector<string> > vvs;
typedef vector<vector<double> > vvd;
typedef vector<vector<Pi> > vvPi;
typedef priority_queue<int, vector<int>, greater<int> > pqli;
typedef priority_queue<ll, vector<ll>, greater<ll> > pqlll;
typedef priority_queue<Pi, vector<Pi>, greater<Pi> > pqlP;
struct Edge {
	int from, to, cost;
	bool operator<(Edge e) {
		return cost < e.cost;
	}
};
typedef vector<Edge> Edges;
typedef vector<Edges> Graph;
template <class T>
using vec = vector<T>;
template<class T>
using pql = priority_queue<T, vector<T>, greater<T>>;
string debug_show(Pi a) {
	return "(" + to_string(a.first) + "," + to_string(a.second) + ")";
}
string YN(bool y) { return (y ? "YES" : "NO"); }
string yn(bool y) { return (y ? "Yes" : "No"); }

template<class T>
struct augEdge {
	T from, to;
	int cost;
	bool operator<(augEdge e) { return cost < e.cost; }
	bool operator>(augEdge e) { return cost > e.cost; }
};
template<class T>
using augGraph = vector<augEdge<T>>;

int dir4[4][2] = { {0,-1},{-1,0},{1,0},{0,1} };
int dir8[8][2] = { {-1,-1},{0,-1},{1,-1},{-1,0},{1,0},{-1,1},{0,1},{1,1} };

class LCA {
public:
	int N;
	vi depth;
	int maxdepth = 0;
	vvi dblpar;
 
	LCA(const Graph &G) {
		N = G.size();
		depth = vi(N);
		dfs1(G, 0, -1);
		int D = bindigit(maxdepth) + 1;
		dblpar = vvi(D, vi(N));
		rep(i, D) {
			dfs2(G, 0, 0, i);
		}
	}
 
	void dfs1(const Graph &G, int pos, int par, int d = 0) {
		depth[pos] = d;
		maxdepth = max(maxdepth, d);
		for (auto e : G[pos]) {
			if (e.to == par)continue;
			dfs1(G, e.to, pos, d + 1);
		}
	}
 
	void dfs2(const Graph &G, int pos, int par, int dbl) {
		if (dbl == 0) {
			dblpar[0][pos] = par;
		}
		else {
			dblpar[dbl][pos] = dblpar[dbl - 1][dblpar[dbl - 1][pos]];
		}
		for (auto e : G[pos]) {
			if (e.to == par)continue;
			dfs2(G, e.to, pos, dbl);
		}
	}
 
	int parent(int pos, int step) {
		int ret = pos;
		int dbl = 0;
		while (step > 0) {
			if ((step & 1) != 0) {
				ret = dblpar[dbl][ret];
			}
			step >>= 1;
			dbl++;
		}
		return ret;
	}
 
	int bindigit(int n) {
		int ret = 0;
		while (n > 0) {
			ret++;
			n >>= 1;
		}
		return ret;
	}
 
	int lca(int u, int v) {
		if (depth[u] < depth[v]) {
			swap(u, v);
		}
		u = parent(u, depth[u] - depth[v]);
		int ng = -1, ok = depth[u];
		while (ok - ng > 1) {
			int mid = (ok + ng) / 2;
			if (parent(u, mid) == parent(v, mid)) {
				ok = mid;
			}
			else {
				ng = mid;
			}
		}
		return parent(u, ok);
	}
 
	int dist(int u, int v) {
		return depth[u] + depth[v] - 2 * depth[lca(u, v)];
	}
};
 
class Lowlink {
public:
	vi ord, low;
	vb vis;
	vi articulation;
	vPi bridge;
	Graph BCCtree;
	vi n2g;
	vvi component;
	set<Pi>bridgeSet;
 
	Lowlink(const Graph &G) {
		ord = vi(G.size());
		low = vi(G.size());
		vis = vb(G.size(), false);
		int k = 0;
		for (int i = 0; i < G.size(); i++) {
			if (!vis[i]) dfs(G, i, -1, k);
		}
	}
 
	void dfs(const Graph &G, int v, int p, int &k)
	{
		vis[v] = true;
 
		ord[v] = k++;
		low[v] = ord[v];
 
		bool isArticulation = false;
		int ct = 0;
 
		for (int i = 0; i < G[v].size(); i++) {
			if (!vis[G[v][i].to]) {
				ct++;
				dfs(G, G[v][i].to, v, k);
				low[v] = min(low[v], low[G[v][i].to]);
				if (p != -1 && ord[v] <= low[G[v][i].to]) isArticulation = true;
				if (ord[v] < low[G[v][i].to]) {
					bridge.push_back(make_pair(min(v, G[v][i].to), max(v, G[v][i].to)));
					bridgeSet.insert(make_pair(min(v, G[v][i].to), max(v, G[v][i].to)));
				}
			}
			else if (G[v][i].to != p) {
				low[v] = min(low[v], ord[G[v][i].to]);
			}
		}
 
		if (p == -1 && ct > 1) isArticulation = true;
		if (isArticulation) articulation.push_back(v);
	}
 
	void bcc(const Graph &G) {
		if (bridge.size() == 0) {
			component = vvi(1);
			n2g = vi(G.size());
			rep(i, G.size()) {
				component[0].push_back(i);
				n2g[i] = 0;
			}
			BCCtree = Graph(1);
			return;
		}
		int k = 0;
		n2g = vi(G.size(), -1);
		vis = vb(G.size(), false);
		component = vvi(G.size());

 
		for (Pi e : bridge) {
			if (!vis[e.first]) {
				bccdfs(G, e.first, -1, k);
				k++;
			}
			if (!vis[e.second]) {
				bccdfs(G, e.second, -1, k);
				k++;
			}
		}
		BCCtree = Graph(k);
		component.resize(k);
		for (Pi e : bridge) {
			int a = n2g[e.first];
			int b = n2g[e.second];
			BCCtree[a].push_back(Edge{ a,b,1 });
			BCCtree[b].push_back(Edge{ b,a,1 });
		}
	}
 
	void bccdfs(const Graph &G, int pos, int par, int k) {
		vis[pos] = true;
		n2g[pos] = k;
		component[k].push_back(pos);
		for (Edge e : G[pos]) {
			if (vis[e.to]) continue;
			if (bridgeSet.find(mp(min(e.from, e.to), max(e.from, e.to))) != bridgeSet.end())continue;
			bccdfs(G, e.to, pos, k);
		}
	}
};
 
int main(void) {
	int N, M;
	cin >> N >> M;
	Graph G(N);
	rep(i, M) {
		int a, b;
		cin >> a >> b;
		a--; b--;
		G[a].push_back(Edge{ a,b,1 });
		G[b].push_back(Edge{ b,a,1 });
	}
 
	Lowlink lowlink(G);
	lowlink.bcc(G);		// 非連結なグラフになっているらしい！ DFSが上手く回っていないっぽい！
 
	cout << lowlink.BCCtree.size() - 1 << endl;

	/*
	rep(i, N) {
		if (!lowlink.vis[i])return 0;
	}
	//*/
 
	/*
	for (auto g : lowlink.n2g) {
		if (g == -1)return 0;
	}
	//*/
 
	/*
	for (auto c : lowlink.component) { for (auto i : c)cout << i << " "; cout << endl; }
	//*/
	/*
	rep(i, lowlink.BCCtree.size()) {
		for (auto e : lowlink.BCCtree[i]) {
			cout << e.from << "->" << e.to << endl;
		}
	}
	//*/
 
 
	/*
	int num = 0;
	rep(i, lowlink.BCCtree.size()) {
		num += lowlink.BCCtree[i].size();
	}
	num /= 2;
	if (num < lowlink.BCCtree.size() - 1)return 0;
	*/
	/*
	LCA lca(lowlink.BCCtree);
	
	int Q;
	cin >> Q;
 
	rep(i, Q) {
		int a, b, c;
		cin >> a >> b >> c;
		a--; b--; c--;
		a = lowlink.n2g[a];
		b = lowlink.n2g[b];
		c = lowlink.n2g[c];
 
		if (lca.dist(a, c) == lca.dist(a, b) + lca.dist(b, c)) {
			cout << "OK" << endl;
		}
		else {
			cout << "NG" << endl;
		}
	}
	//*/

	return 0;
}

Measured memory_bytes: 256