//#pragma warning(disable:4996)
//#include <Windows.h>
#include <iostream>
#include <vector>
#include <limits.h>
#include <algorithm>
#include <string>
#include <math.h>
#include <limits.h>
#include <queue>
#include <map>
#include <set>
#include <iomanip>
#include <bitset>
#include <cassert>
#include <random>
#include <functional>
#include <stack>
#include <iomanip>
#include <cassert>
//#include <boost/multiprecision/cpp_int.hpp>
#include <complex>
#include <cstdio>
#include <list>
#include <bitset>
//#include <stdio.h>

//< in.txt > out.txt
using namespace std;
//std::ios::sync_with_stdio(false);
//std::cin.tie(0);
const long long MOD = 1e9 + 7;
const long long INF = 1e18;
typedef long long LL;
typedef long double LD;
//typedef boost::multiprecision::cpp_int bigint;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PI;
typedef pair<LD, LL> pdl;
typedef pair<LD, LD> pdd;
typedef vector<LL> VLL;
typedef vector<VLL> VVLL;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
typedef unsigned long long ULL;
template<class T>
using pqueue = priority_queue<T, vector<T>, function<bool(T, T)>>;

template<class T>
inline void chmin(T& a, T b) {
	a = min(a, b);
}

template<class T>
inline void chmax(T& a, T b) {
	a = max(a, b);
}

void input();
void solve();

void daminput();
void naive();

void outputinput();

int main() {
	std::cin.tie(0);
	std::ios::sync_with_stdio(false);
	input();
	//daminput();
	solve();
	//naive();
	//outputinput();
	return 0;
}

//////////////////////////////////////////////////
//////////////////////////////////////////////////

int N, M;
VI A, B;

void input() {
	cin >> N >> M;
	A.resize(M);
	B.resize(M);
	for (int m = 0; m < M; m++) {
		cin >> A[m] >> B[m];
	}
}

void daminput() {
}

template<class T>
struct Gedge {
	int src, to;
	T cost;
	int id;
	Gedge(int s, int t, T c, int i = -1) :src(s), to(t), cost(c), id(i) {}
	Gedge(int t, T c) :src(-1), to(t), cost(c), id(-1) {}
};

template<class T>
using WeightedGraph = vector<vector<Gedge<T>>>;
using UnweightedGraph = vector<vector<LL>>;
template<class T>
using Gedges = vector<Gedge<T>>;

template<class T>
struct TNode {
	int parent;
	VI childs;
	T cost;
	int id;
	TNode() :parent(-1), cost(0), id(-1) {};
};
template<class T>
using Tree = vector<TNode<T>>;

template<class T>
struct DFSTreeNode {
	vector<Gedge<T>> childs;   //自分から子に行く辺集合
	int ord, lowlink;
	int dece;   //何番目の二重連結成分に含まれるか
	DFSTreeNode() : ord(-1), lowlink(-1), dece(-1) {};
};
template<class T>
using DFSTree = vector<DFSTreeNode<T>>;

template<class T>
void BridgeDECEdfs(WeightedGraph<T>& G, DFSTree<T>& tree, int n, int& ord, vector<vector<Gedge<T>>>& backedges, Gedge<T> edge) {
	if (tree[n].ord != -1)return;
	tree[n].ord = ord++;
	tree[n].lowlink = tree[n].ord;
	for (Gedge<T>& next : G[n]) {
		if (tree[next.to].ord != -1) {
			//行き先のordがすでに決まっている場合
			//親→子の辺だったら無視
			if (next.id == edge.id)continue;
			backedges[n].push_back(next);
			tree[n].lowlink = min(tree[n].lowlink, tree[next.to].ord);
		}
		else {
			tree[n].childs.push_back(next);
			BridgeDECEdfs(G, tree, next.to, ord, backedges, next);
			tree[n].lowlink = min(tree[n].lowlink, tree[next.to].lowlink);
		}
	}
}

//無向グラフGを受け取りDFS木と橋のidの集合bridges、各dece要素に属する頂点集合deceを返す
template<class T>
void BridgeDECE(WeightedGraph<T>& G, DFSTree<T>& tree, set<int>& bridges, VVI& dece, int start = 0) {
	tree.resize(G.size());
	for (int n = 0; n < G.size(); n++)tree[n].ord = -1;
	vector<vector<Gedge<T>>> backedges(G.size());   //後退辺の集合
	int ord = 0;   //次設定するord
	//ord,lowlink求める
	BridgeDECEdfs(G, tree, start, ord, backedges, Gedge<T>(-1, start, 0, -1));
	//連結要素id
	int id = 0;
	stack<int> st;
	for (int n = 0; n < G.size(); n++) {
		if (tree[n].dece != -1)continue;
		st.push(n);
		dece.push_back(VI());
		while (!st.empty()) {
			int cur = st.top();
			st.pop();
			if (tree[cur].dece != -1)continue;
			tree[cur].dece = id;
			dece.back().push_back(cur);
			//子
			for (Gedge<T>& edge : tree[cur].childs) {
				//辺が橋である条件:DFS木の辺かつ子のlowlink > 親のord
				if (tree[cur].ord < tree[edge.to].lowlink) {
					//橋だった
					bridges.insert(edge.id);
				}
				else {
					st.push(edge.to);
				}
			}
			//後退辺
			for (Gedge<T>& back : backedges[cur]) {
				st.push(back.to);
			}
		}
		id++;
	}
}

template<class T>
void JointPoint(DFSTree<T>& tree, VI& jpoints, int start = 0) {
	if (tree[start].childs.size() > 1) {
		jpoints.push_back(start);
	}
	//関節点の条件:
	//point v is a jointpoint
	// <==> \exist u \in v.childs,u.ord <= v.lowlink
	for (int n = 0; n < tree.size(); n++) {
		if (n == start)continue;
		for (Gedge<T>& edge : tree[n].childs) {
			if (tree[edge.to].lowlink >= tree[n].ord) {
				jpoints.push_back(n);
				break;
			}
		}
	}
}

void solve() {
	WeightedGraph<int> G(N);
	for (int m = 0; m < M; m++) {
		G[A[m] - 1].push_back(Gedge<int>(A[m] - 1, B[m] - 1, 1, m));
		G[B[m] - 1].push_back(Gedge<int>(B[m] - 1, A[m] - 1, 1, m));
	}
	DFSTree<int> dfstree;
	set<int> bridges;
	VVI dece;
	BridgeDECE(G, dfstree, bridges, dece);
	cout << bridges.size() << "\n";
}

void naive() {
}

void outputinput() {
}

Measured memory_bytes: 3204