CodeEff/ECCO huggingface dataset:
# Original solution
from queue import Queue
N, M = list(map(int, input().split()))
a, b = [0] * M, [0] * M
g = [[False] * N for _ in range(N)]
for i in range(M):
    a[i], b[i] = list(map(int, input().split()))
    g[a[i]-1][b[i]-1] = True
    g[b[i]-1][a[i]-1] = True

ans = 0
for _a, _b in zip(a, b):
    g[_a-1][_b-1] = False
    g[_b-1][_a-1] = False
    visit = [False] * N
    q = Queue()
    q.put(0)
    while not q.empty():
        v = q.get()
        visit[v] = True
        for adj_v, adj in enumerate(g[v]):
            if not adj or visit[adj_v]:
                continue
            q.put(adj_v)
    if not all(visit):
        ans += 1
    g[_a-1][_b-1] = True
    g[_b-1][_a-1] = True
print(ans)

# Optimized solution
class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self._size = n

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            self._size -= 1
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def size(self):
        return self._size

N, M = list(map(int, input().split()))
a, b = [0] * M, [0] * M
for i in range(M):
    a[i], b[i] = list(map(int, input().split()))

ans = 0
for i in range(M):
    uf = UnionFind(n=N)
    for j in range(M):
        if j == i:
            continue
        uf.union(a[j]-1, b[j]-1)
    if uf.size() != 1:
        ans += 1
print(ans)
# Original solution
N, M = list(map(int,input().split()))                                         # N:ノード数、M:エッジ数
visited = [False for i in range(N)]                                     # ノードの訪問済みフラグ
edges = [[i-1 for i in map(int,input().split())] for j in range(M)]     # エッジ情報のインプット
graph = [[False for i in range(N)] for j in range(N)]                   # 接続行列の入れ物

# 各エッジについて、情報を接続行列に埋め込む
for edge in edges:
    graph[edge[0]][edge[1]] = True
    graph[edge[1]][edge[0]] = True

# dfs:ノードvについて、エッジで接続した未訪問のノードを即時訪問する(再帰処理)
def dfs(v):
    queue = [v]
    while len(queue) != 0:
        now = queue.pop(0)
        visited[now] = True
        for next in range(N):
            if graph[now][next] == False:continue
            if visited[next] == True:continue
            queue.append(next)

ans = 0

# 各エッジを取り除いたとき、dfsして未訪問のノードが残っていれば、そのエッジは橋
for i in range(M):
    graph[edges[i][0]][edges[i][1]] = False
    graph[edges[i][1]][edges[i][0]] = False

    for j in range(N):
        visited[j] = False

    dfs(0)

    bridge = False
    for j in range(N):
        if visited[j] == False:
            bridge = True
    if bridge:
        ans += 1

    graph[edges[i][0]][edges[i][1]] = True
    graph[edges[i][1]][edges[i][0]] = True

print(ans)


# Optimized solution
N, M = list(map(int,input().split()))                                         # N:ノード数、M:エッジ数
visited = [False for i in range(N)]                                     # ノードの訪問済みフラグ
edges = [[i-1 for i in map(int,input().split())] for j in range(M)]     # エッジ情報のインプット
graph = [[False for i in range(N)] for j in range(N)]                   # 接続行列の入れ物

# 各エッジについて、情報を接続行列に埋め込む
for edge in edges:
    graph[edge[0]][edge[1]] = True
    graph[edge[1]][edge[0]] = True

# dfs:ノードvについて、エッジで接続した未訪問のノードを深いほうから訪問(LIFO)
def dfs(v):
    queue = [v]
    while len(queue) != 0:
        now = queue.pop()
        visited[now] = True
        for next in range(N):
            if graph[now][next] == False:continue
            if visited[next] == True:continue
            queue.append(next)

ans = 0

# 各エッジを取り除いたとき、dfsして未訪問のノードが残っていれば、そのエッジは橋
for i in range(M):
    graph[edges[i][0]][edges[i][1]] = False
    graph[edges[i][1]][edges[i][0]] = False

    for j in range(N):
        visited[j] = False

    dfs(0)

    bridge = False
    for j in range(N):
        if visited[j] == False:
            bridge = True
    if bridge:
        ans += 1

    graph[edges[i][0]][edges[i][1]] = True
    graph[edges[i][1]][edges[i][0]] = True

print(ans)
# Original solution
# coding: utf-8
N, M = list(map(int, input().split()))
graph = [[False for i in range(N)] for j in range(N)]
visited = [False for i in range(N)]
def dfs(v):
    visited[v] = True
    for k in range(N):
        if not graph[v][k]:
            continue
        if visited[k]:
            continue
        dfs(k)
A, B = [], []
for i in range(M):
    a, b = list(map(int, input().split()))
    a -= 1
    b -= 1
    A.append(a)
    B.append(b)
    graph[a][b] = True
    graph[b][a] = True 

ans = 0
for a, b in zip(A, B):
    graph[a][b] = False
    graph[b][a] = False
    visited = [False for i in range(N)]
    dfs(0)
    bridge = False
    for j in range(N):
        if not visited[j]:
            bridge = True
    if bridge:
        ans += 1
    graph[a][b] = True
    graph[b][a] = True
print(ans)



# Optimized solution
# coding: utf-8
N, M = list(map(int, input().split()))
graph = [[False for i in range(N)] for j in range(N)]
visited = [False for i in range(N)]
def dfs(v):
    visited[v] = True
    for k in range(N):
        # 橋がなければわたらない
        if not graph[v][k]:
            continue
        # すでに訪れていたら渡らない
        if visited[k]:
            continue
        dfs(k)
A, B = [], []
for i in range(M):
    a, b = list(map(int, input().split()))
    a -= 1
    b -= 1
    A.append(a)
    B.append(b)
    # 橋をTrueとして表す
    graph[a][b] = True
    graph[b][a] = True 

ans = 0
# a, bの橋が通れないとして都度DFSを実行
for a, b in zip(A, B):
    graph[a][b] = False
    graph[b][a] = False
    visited = [False for i in range(N)]
    dfs(0)
    bridge = False
    for j in range(N):
        if not visited[j]:
            bridge = True
    # 渡れない島があったら+1
    if bridge:
        ans += 1
    graph[a][b] = True
    graph[b][a] = True
print(ans)
# Original solution
N, M = list(map(int, input().split()))
E = [list(map(int, input().split())) for k in range(M)]

ans = 0
for k in range(M):
    F = E[:k]+E[k+1:]
    D = [[10**9+7 for l in range(N)] for m in range(N)]
    for p in range(N):
        D[p][p] = 0
    for n in F:
        D[n[0]-1][n[1]-1] = D[n[1]-1][n[0]-1] = 1
    for s in range(N):
        for t in range(N):
            for u in range(N):
                if D[t][u] > D[t][s] + D[s][u]:
                    D[t][u] = D[t][s] + D[s][u]
    f = 0
    for p in range(N):
        for q in range(N):
            if D[p][q] > N:
                f = 1
    ans += f
print(ans)

# Optimized solution
N, M = list(map(int,input().split()))
P = [list(map(int,input().split())) for k in range(M)]
ans = 0

for i in range(M):
    T = P[:i]+P[i+1:]
    G = [[] for k in range(N)]
    V = [0 for k in range(N)]
    for e in T:
        G[e[0]-1].append(e[1]-1)
        G[e[1]-1].append(e[0]-1)

    def dfs(visited,graph,ima):
        for e in graph[ima]:
            if visited[e] == 0:
                visited[e] = 1
                dfs(visited,graph,e)
    dfs(V,G,0)
    if sum(V) != N:
        ans += 1
print(ans)
# Original solution
import copy
N,M=list(map(int,input().split()))
A=[]
L=[[1000for i in range(N)]for j in range(N)]
for i in range(M):
    a,b=list(map(int,input().split()))
    A.append([a,b])
    L[a-1][b-1]=1
    L[b-1][a-1]=1
    
for j in range(N):
    L[j][j]=0

#print(L)
l=copy.deepcopy(L)

cnt=0
for k in range(M):
    L=copy.deepcopy(l)
    L[A[k][0]-1][A[k][1]-1]=1000
    L[A[k][1]-1][A[k][0]-1]=1000
    for x in range(N):
        for y in range(N):
            for z in range(N):
                if L[y][z]>L[y][x]+L[x][z]:
                    L[y][z]=L[y][x]+L[x][z]
    #print(L[A[k][0]-1][A[k][1]-1])
    if L[A[k][0]-1][A[k][1]-1]>=1000:
        cnt+=1
    L[A[k][0]-1][A[k][1]-1]=1
    L[A[k][1]-1][A[k][0]-1]=1
#print(L)   
print(cnt)

# Optimized solution
import copy
N,M=list(map(int,input().split()))
A=[]

for i in range(M):
    a,b=list(map(int,input().split()))
    A.append([a,b])
    
ANS=0
for i in range(M):
    L=[[]for _ in range(N+1)]
    for j in range(M):
        if i==j:
            continue
        else:
            L[A[j][0]].append((A[j][1],A[j][0]))
            L[A[j][1]].append((A[j][0],A[j][1]))
    X=1
    C=[100 for i in range(N+1)]
    C[1]=1
    C[0]=1
    Q=L[1]
    #print(L)
    for j in range(1000):
        if len(Q)==j:
            break
        if C[Q[j][0]]==100:
            C[Q[j][0]]=C[Q[j][1]]+1
            for k in L[Q[j][0]]:
                if C[k[0]]==100:
                    Q.append((k[0],k[1]))
    #print(Q)
    #print(C)
    if 100 in C:
        ANS+=1
print(ANS)
# Original solution
n,m=list(map(int,input().split()))

g=[[] for i in range(1+n)]
v=[0]*(1+n)
e=[]
for i in range(m):
    a,b=list(map(int,input().split()))
    e.append((a,b))
    g[a].append(b)
    g[b].append(a)

def dfs(st,ed):
    q=[]
    q.append(st)

    while q:
        cur=q.pop()
        v[cur]=1
        if cur==ed:
            return True

        for nb in g[cur]:
            #if v[nb]!=1:
            if v[nb]!=1 and (cur,nb)!=(st,ed):
                q.append(nb)
    #print(st,ed,v)
    return False

ans=0
for a,b in e:
    v=[0]*(1+n)
    #v[b]=1
    if not dfs(a,b):
        ans+=1
#print(g)
print(ans)


# Optimized solution
n,m=list(map(int,input().split()))

g=[[] for i in range(1+n)]
v=[0]*(1+n)
e=[]
for i in range(m):
    a,b=list(map(int,input().split()))
    e.append((a,b))
    g[a].append(b)
    g[b].append(a)

def dfs(st,ed):
    q=[]
    q.append(st)

    while q:
        cur=q.pop()
        v[cur]=1
        if cur==ed:
            return True

        for nb in g[cur]:
            if v[nb]!=1 and (cur,nb)!=(st,ed):
                q.append(nb)
    return False

ans=0
for a,b in e:
    v=[0]*(1+n)
    if not dfs(a,b):
        ans+=1
print(ans)
# Original solution
from collections import deque
N, M = list(map(int, input().split()))

V = [list(map(int, input().split())) for _ in range(M)]

class UnionFind():
    def __init__(self, n):
        self.parents = [-1] * n

    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return False
        if self.parents[x] > self.parents[y]:
            x, y = y, x

        self.parents[x] += self.parents[y]
        self.parents[y] = x
        return True

    def size(self, x):
        return -self.parents[self.find(x)]

    def same(self, x, y):
        return self.find(x) == self.find(y)

ans = 0
for i in range(N):
    for j in range(i+1, N):
        uf = UnionFind(N)
        for a, b in V:
            if a-1 == i and b-1 ==j:
                continue
            uf.union(a-1, b-1)
        if not uf.same(i, j):
            ans += 1
print(ans)


# Optimized solution
from collections import deque
N, M = list(map(int, input().split()))

V = [list(map(int, input().split())) for _ in range(M)]

class UnionFind():
    def __init__(self, n):
        self.parents = [-1] * n

    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return False
        if self.parents[x] > self.parents[y]:
            x, y = y, x

        self.parents[x] += self.parents[y]
        self.parents[y] = x
        return True

    def size(self, x):
        return -self.parents[self.find(x)]

    def same(self, x, y):
        return self.find(x) == self.find(y)

ans = 0
for i in range(M):
        uf = UnionFind(N)
        for j, (a, b) in enumerate(V):
            if i != j:
                uf.union(a-1, b-1)
        if not uf.same(V[i][0]-1, V[i][1]-1):
            ans += 1
print(ans)
# Original solution
class UnionFind():
	def __init__(self, n):
		self.n = n
		self.root = [-1]*(n+1)
		self.rank = [0]*(n+1)

	def find(self, x):
		if(self.root[x] < 0):
			return x
		else:
			self.root[x] = self.find(self.root[x])
			return self.root[x]
			
	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if(x == y):
			return 0
		elif(self.rank[x] > self.rank[y]):
			self.root[x] += self.root[y]
			self.root[y] = x
		else:
			self.root[y] += self.root[x]
			self.root[x] = y
			if(self.rank[x] == self.rank[y]):
				self.rank[y] += 1

	def size(self, x):
		return -self.root[self.find(x)]
		
n,m,*l = list(map(int,open(0).read().split()))
edges = [(a,b) for a,b in zip(l[::2],l[1::2])]
ans = 0
for i in range(m):
	uf = UnionFind(n)
	for j in range(m):
		if i==j:
			continue
		else:
			a,b = edges[j]
			uf.unite(a,b)
	ans += uf.size(a)<n
print(ans)

# Optimized solution
import sys
input = sys.stdin.buffer.readline
n,m=list(map(int,input().split()))
es=[list(map(int,input().split())) for _ in range(m)]
def check(s,t):
	q=[0]
	vis=[0]*n
	vis[0]=1
	while q:
		c=q.pop()
		for x in con[c]:
			if (c==s and x==t) or (c==t and x==s) or vis[x]:
				continue
			vis[x]=1
			q.append(x)
	return not all(vis)
con=[[] for _ in range(n)]
for s,t in es:
	con[s-1]+=t-1,
	con[t-1]+=s-1,
print((sum(check(s-1,t-1) for s,t in es)))
# Original solution
from collections import deque

n,m = list(map(int,input().split()))
G = [[0]*n for _ in range(n)]
E = []
for i in range(m):
    a,b = list(map(int,input().split()))
    a,b = a-1, b-1
    G[a][b] = 1
    G[b][a] = 1
    E.append((a,b))

ans = 0
for e in E:
    del_a, del_b = e
    
    #対象の辺を消す
    G[del_a][del_b] = 0
    G[del_b][del_a] = 0
    
    que = ([0])
    visited = [False]*n
    while que:
        node = que.pop()
        if visited[node] == True:
            continue
        visited[node] = True
        for j in range(n):
            if G[node][j] == 1:
                que.append(j)
    if False in visited:
        ans += 1
    
    #対象の辺を戻す
    G[del_a][del_b] = 1
    G[del_b][del_a] = 1
print(ans)

# Optimized solution
class UnionFind:
    #要素数nで初期化
    #インスタンス変数の初期化
    def __init__(self, n):
        #root : 木の要素数
        #rnk : 木の高さ(rank)
        self.n = n
        self.root = [-1]*(n+1)
        self.rnk = [0]*(n+1)
    
    """ノードxのrootノードを見つける"""
    def Find_Root(self, x):
        if self.root[x] < 0:
            return x
        else:
            self.root[x] = self.Find_Root(self.root[x]) 
            return self.root[x]
    
    """木の合併"""
    def Union(self, x, y):
        #入力ノードの親を探索
        x = self.Find_Root(x)
        y = self.Find_Root(y)
        
        #既に同じ木に所属する場合
        if (x==y):
            return 
        
        #異なる木に所属する場合 -> rnkから判断して合併する
        elif (self.rnk[x] > self.rnk[y]):
            self.root[x] += self.root[y]
            self.root[y] = x
        
        else:
            self.root[y] += self.root[x]
            self.root[x] = y
            
            #rnkが同じ(深さが同じ)場合
            if self.rnk[x]==self.rnk[y]:
                self.rnk[y] += 1
    
    """xとyが同じグループに所属するか"""
    def isSameGroup(self, x, y):
        return self.Find_Root(x) == self.Find_Root(y)
    
    """ノードxが属する木のサイズ"""
    def Count(self, x):
        return -self.root[self.Find_Root(x)]


n,m = list(map(int,input().split()))
edge = []
for i in range(m):
    a,b = list(map(int,input().split()))
    edge.append([a,b])

res = 0
for i in range(m):
    tmp_edge = edge[:i] + edge[i+1:]
    uf = UnionFind(n)
    for j in range(len(tmp_edge)):
        uf.Union(tmp_edge[j][0], tmp_edge[j][1])
    if not uf.isSameGroup(edge[i][0], edge[i][1]):
        res += 1
print(res)
# Original solution
N, M = list(map(int, input().split()))
edge = [[] for _ in range(N)]
edges = [(0,0) for _ in range(M)]
for i in range(M):
    a,b = list(map(int, input().split()))
    edge[a-1].append(b-1)
    edge[b-1].append(a-1)
    edges[i] = (a-1,b-1)

import sys
sys.setrecursionlimit(10**8)

def dfs(v, es):
    #print(h,w)
    if len(es[v])==0:
        return 0
    for u in es[v]:
        if visited[u]==False:
            visited[u]=True
            dfs(u,es)
    return 0

import copy
ans = 0
for i in range(M):
    visited = [False]*N
    a,b = edges[i]
    es = copy.deepcopy(edge)
    # print(edge,a,b)
    es[a].remove(b)
    es[b].remove(a)
    for v in range(N):
        if visited[v]==False:
            visited[v]=True
            dfs(v,es)
            if False in visited:
                # print(a,b)
                break
    else:
        continue
    ans += 1
print(ans)

# Optimized solution
class UnionFind():
    def __init__(self, n):
        self.n = n
        self.parents = [-1] * n

    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
        return self.parents[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.parents[x] > self.parents[y]:
                x, y = y, x

        self.parents[x] += self.parents[y]
        self.parents[y] = x

    def same(self, x, y):
        return self.find(x) == self.find(y)

    def nums(self,x):
        return abs(self.parents[self.find(x)])

    def roots(self):
        return [i for i, x in enumerate(self.parents) if x < 0]

    def members(self, x):
        root = self.find(x)
        return [i for i in range(self.n) if self.find(i) == root]

    def __str__(self):
        return '\n'.join('{}: {}'.format(r, self.members(r)) for r in self.roots())

N, M = list(map(int, input().split()))
A = [0]*M
B = [0]*M
for i in range(M):
    A[i],B[i] = list(map(int, input().split()))
ans = 0
for i in range(M):
    uf = UnionFind(N)
    for j in range(M):
        if j!=i:
            uf.union(A[j]-1,B[j]-1)
    if len(uf.roots())>1:
        ans += 1
print(ans)
# Original solution
#!/usr/bin/env python3

from pprint import pprint
from collections import deque, defaultdict
import itertools
import math
import sys

sys.setrecursionlimit(10 ** 6)
input = sys.stdin.buffer.readline
INF = float('inf')

n_nodes, m_edges = list(map(int, input().split()))
edges = []
for _ in range(m_edges):
    u, v = list(map(int, input().split()))
    edges.append([u - 1, v - 1])
# pprint(edges)


def find_root(v):
    if v != parents[v]:
        parents[v] = find_root(parents[v])
    return parents[v]


def has_same_root(u, v):
    return find_root(u) == find_root(v)


def unite(u, v):
    root_u, root_v = find_root(u), find_root(v)
    if root_u == root_v:
        return
    if rank[root_u] < rank[root_v]:
        root_u, root_v = root_v, root_u
    parents[root_v] = root_u
    rank[root_u] += rank[root_v]


res = 0
for i in range(m_edges):
    parents = [i for i in range(n_nodes)]
    rank = [1] * n_nodes
    for j in range(m_edges):
        if i == j:
            continue
        u, v = edges[j]
        if not has_same_root(u, v):
            unite(u, v)
    # pprint(parents)
    count = 0
    for v in range(n_nodes):
        if parents[v] == v:
            count += 1
    if count > 1:
        res += 1

print(res)


# Optimized solution
#!/usr/bin/env python3

from pprint import pprint
import sys

sys.setrecursionlimit(10 ** 6)
INF = float('inf')


N, M = list(map(int, input().split()))

edges = []
for _ in range(M):
    u, v = list(map(int, input().split()))
    edges.append([u-1, v-1])
# pprint(edges)


# ある辺iを除いた残りの辺にそれぞれに対してuniteの操作を行ったのち、異なるparentsの数を数える
# 連結グラフであれば、異なるparentsの数は1になる
# 非連結グラフであれば、異なるparentsの数は2以上になる


def root(v):
    if parents[v] == v:
        return v
    parents[v] = root(parents[v])
    return parents[v]


def unite(u, v):
    u_root = root(u)
    v_root = root(v)
    if u_root == v_root:
        return
    parents[u_root] = v_root


def same(u, v):
    return root(u) == root(v)


ans = 0
for e_i in edges:
    parents = [i for i in range(N)]
    for e_j in edges:
        if e_i == e_j:
            continue
        u, v = e_j
        if not same(u, v):
            unite(u, v)
    count = 0
    for v in range(N):
        if parents[v] == v:
            count += 1
    if count > 1:
        ans += 1

print(ans)
# Original solution
n,m=list(map(int,input().split()))
e=[[] for _ in range(n)]
b=[]

for i in range(m):
  x,y=list(map(int,input().split()))
  e[x-1].append(y-1)
  e[y-1].append(x-1)
  b.append([x-1,y-1])

def ne(now,s,visit):
  visit.append(now)
  for i in e[now]:
    if i not in visit:
      ne(i, s, visit[:])
    elif len(visit)>2 and s==i:
      v1=visit[1]
      v2=visit[-1]
      if [s,v1] in b: b.remove([s,v1])
      elif [v1,s] in b: b.remove([v1,s])
      if [s,v2] in b:b.remove([s,v2])
      elif [v2,s] in b:b.remove([v2,s])
      break

for i in range(n):
  ne(i,i,[])
print((len(b)))

# Optimized solution
from itertools import combinations
class UnionFind():
    def __init__(self, n):
        self.n = n
        self.root = [-1]*(n+1)
        self.rnk = [0]*(n+1)
    def Find_Root(self, x):
        if(self.root[x] < 0):
            return x
        else:
            self.root[x] = self.Find_Root(self.root[x])
            return self.root[x]
    def Unite(self, x, y):
        x = self.Find_Root(x)
        y = self.Find_Root(y)
        if(x == y):
            return 
        elif(self.rnk[x] > self.rnk[y]):
            self.root[x] += self.root[y]
            self.root[y] = x
        else:
            self.root[y] += self.root[x]
            self.root[x] = y
            if(self.rnk[x] == self.rnk[y]):
                self.rnk[y] += 1
    def isSameGroup(self, x, y):
        return self.Find_Root(x) == self.Find_Root(y)
    def Count(self, x):
        return -self.root[self.Find_Root(x)]

n,m=list(map(int,input().split()))
uf=UnionFind(n)
e=[]
comb=list(combinations(list(range(n)),2))
for _ in range(m):
  a,b=list(map(int,input().split()))
  e.append((a-1,b-1))
ans=0
for i in range(m):
  uf=UnionFind(n)
  for j in range(m):
    if i==j:continue
    uf.Unite(*e[j])
  if not all([uf.isSameGroup(x,y) for x,y in comb]):ans+=1
print(ans)
# Original solution
def root(i):
    if par[i] < 0:
        return i
    else:
        return root(par[i])

def size(a):
    return -par[root(a)]

def union(a,b):
    a = root(a)
    b = root(b)
    if a == b:#親が等しい
        return False
    if size(a) < size(b):#サイズが大きい方に繋げる
        a,b = b,a
    par[a] += par[b]
    par[b] = a
    return True

n,m = list(map(int,input().split()))
bridge = []
for i in range(m):
    bridge.append([int(j)-1 for j in input().split()])
ans = 0
for i in range(m):
    par = [-1 for _ in range(n)]
    for j in range(m):
        if j != i:
            union(bridge[j][0],bridge[j][1])
    cnt = [ i for i in par if i < 0]
    if len(cnt) > 1:
        ans += 1
print(ans)

# Optimized solution
def dfs(current):
    for maps in Graph[current]:
        if maps in already:
            continue
        else:
            already.append(maps)
            dfs(maps)

n,m = list(map(int,input().split()))
ans = 0
Edges = []
for _ in range(m):
    Edges.append([int(j)-1 for j in input().split()])

for i in range(m):
    egdes = Edges[0:i]+Edges[i+1:]
    Graph = [[] for _ in range(n)]
    for edge in egdes:
        Graph[edge[0]].append(edge[1])
        Graph[edge[1]].append(edge[0])
    already = []
    dfs(0)
    if len(already) != n:
        ans += 1
print(ans)