CodeEff/ECCO huggingface dataset:
# Original solution
from sys import setrecursionlimit
#setrecursionlimit(10**5)
class UnionFind:
    def __init__(self, n):
        # 親要素のノード番号を格納
        # par[x]=xの時そのノードは根　
        self.par = [i for i in range(n + 1)]
        # rank
        self.rank = [0] * (n + 1)
        self.size = [1] * (n + 1)

    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 check(self, x, y):
        # x,yが同じ集合に属するかどうか調べる
        return self.find(x) == self.find(y)


    def union(self, x, y):
        # 根を探す
        x = self.find(x)
        y = self.find(y)
        if x == y:return
        # rankの小さいものを大きいほうにくっつける
        if self.rank[x] < self.rank[y]:
            self.par[x] = y
            self.size[y] += self.size[x]
        else:
            self.par[y] = x
            self.size[x] += self.size[y]
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1

    def count_group(self):
        return len({self.find(x) for x in range(1, n + 1)})

n, m = list(map(int,input().split()))
ab = [list(map(int,input().split())) for i in range(m)]

cnt = 0
for i in range(m):
    uf = UnionFind(n)
    for j in range(m):
        if i == j:continue
        a, b = ab[j]
        uf.union(a, b)

    if uf.count_group() > 1:cnt += 1
print(cnt)

# Optimized solution
from sys import setrecursionlimit
#setrecursionlimit(10**5)
class UnionFind:
    def __init__(self, n):
        # 親要素のノード番号を格納
        # par[x]=xの時そのノードは根　
        self.par = [i for i in range(n + 1)]
        # rank
        self.rank = [0] * (n + 1)
        self.size = [1] * (n + 1)

    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 check(self, x, y):
        # x,yが同じ集合に属するかどうか調べる
        return self.find(x) == self.find(y)


    def union(self, x, y):
        # 根を探す
        x = self.find(x)
        y = self.find(y)
        if x == y:return
        # rankの小さいものを大きいほうにくっつける
        if self.rank[x] < self.rank[y]:
            self.par[x] = y
            self.size[y] += self.size[x]
        else:
            self.par[y] = x
            self.size[x] += self.size[y]
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1

    def count_group(self, n):
        return len({self.find(x) for x in range(1, n + 1)})

n, m = list(map(int,input().split()))
ab = [list(map(int,input().split())) for i in range(m)]

cnt = 0
for i in range(m):
    uf = UnionFind(n)
    for j in range(m):
        if i == j:continue
        a, b = ab[j]
        uf.union(a, b)

    if uf.count_group(n) != 1:cnt += 1
print(cnt)
# Original solution
from collections import deque
from copy import deepcopy

N,M = list(map(int,input().split()))
Matrix = [[False for x in range(N)] for y in range(N)]

Path = []
for i in range(M) :
    a,b = list(map(int,input().split()))
    a -= 1
    b -= 1
    Path.append((a,b))
    Matrix[a][b] = True
    Matrix[b][a] = True

ans = 0
for i in range(M) :
    Matrix_duplicated = deepcopy(Matrix)
    a,b = Path[i]
    Matrix_duplicated[a][b] = False
    Matrix_duplicated[b][a] = False
    Visited = []
    Queue = deque([0])
    while Queue :
        now = Queue.popleft()
        Visited.append(now)
        for i in range(N) :
            if Matrix_duplicated[now][i] :
                Matrix_duplicated[now][i] = False
                Matrix_duplicated[i][now] = False
                Queue.append(i)
    if len(list(set(Visited))) != N :
        ans += 1
print(ans)


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

    def find_root(self, node):
        if self.par[node] == node:
            return node
        else:
            self.par[node] = self.find_root(self.par[node])
            return self.par[node]

    def same_root(self, x, y):
        return self.find_root(x) == self.find_root(y)

    def union(self, x, y):
        x = self.find_root(x)
        y = self.find_root(y)
        if x == y:
            return
        if self.rank[x] > self.rank[y]:
            self.par[y] = x
        else:
            self.par[x] = y
            if self.rank[x] == self.rank[y]:
                self.rank[y] += 1


def main():
    N, M = list(map(int, input().split()))
    L = [list(map(int,input().split())) for i in range(M)]
    for i in range(M) :
        L[i][0] -= 1
        L[i][1] -= 1
    
    ans = 0
    
    for i in range(M) :
        UF = UnionFind(N)
        for j in range(M) :
            if j == i :
                continue
            UF.union(L[j][0], L[j][1])
        
        ans += (UF.same_root(L[i][0], L[i][1]) == False)
    
    print(ans)

import sys
input = sys.stdin.readline

if __name__ == "__main__":
    main()
# Original solution
N, M = list(map(int,input().split()))

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

def dfs(v, e):
  for num in v:
    if(len(e[num]) == 1):
      tmp_num = e[num][0]
      v.remove(num)
      e[num] = []
      e[tmp_num].remove(num)
      #print(e,v)
      return dfs(v, e)+1
  return 0
print((dfs(v, e)))

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

v = list(range(1,N+1))
edges = [[] for _ in range(N+1)]
for _ in range(M):
  a, b = list(map(int,input().split()))
  edges[a] += [b]
  edges[b] += [a]

def dfs(v, e):
  for x in v:
    if(len(e[x]) == 1):
      t = e[x][0]
      e[x] = []
      e[t].remove(x)
      v.remove(x)
      return dfs(v, e)+1
  return 0

print((dfs(v, edges)))
# Original solution
from collections import defaultdict
from copy import deepcopy


def dfs(graph, v, visited):
    visited.add(v)
    for nv in graph[v]:
        if nv not in visited:
            dfs(graph, nv, visited)
    return visited


def is_connected(graph, nodes_cnt):
    visited = dfs(graph, 1, set())
    if len(visited) == nodes_cnt:
        return True
    else:
        return False


N, M = list(map(int, input().split()))
edges = [list(map(int, input().split())) for _ in range(M)]
G = defaultdict(set)
for u, v in edges:
    G[u].add(v)
    G[v].add(u)
ans = 0
for u, v in edges:
    H = deepcopy(G)
    H[u] -= {v}
    H[v] -= {u}
    if not is_connected(H, N):
        ans += 1
print(ans)


# Optimized solution
class DisjointSet:
    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 size(self, x):
        return -self.parents[self.find(x)]


N, M, *ab = list(map(int, open(0).read().split()))
edges = [(a - 1, b - 1) for a, b in zip(*[iter(ab)] * 2)]

ans = 0
for i in range(M):
    ds = DisjointSet(N)
    for j in range(M):
        if i == j:
            continue
        ds.union(*edges[j])
    if len([-p for p in ds.parents if p < 0]) > 1:
        ans += 1
print(ans)
# Original solution
n,m=[int(i) for i in input().split()]
pp=[[0 for i in range(2)] for j in range(m)]
ans=0

#深さ優先探索---------------------------------
#参照元：Python3でグラフの実装＆深さ優先探索・幅優先探索の実装
#http://muromura.hatenablog.com/entry/2017/07/16/025414
def dfs(Graph, Start, Vn, Visited=None):
  
##追加（空から読んでも動くように修正）-----------------------
  if Graph[Start]==[] and Visited==None: 
        Next=Start+1        
        dfs(Graph, Next, Vn, Visited)  #再帰
  else:
##--------------------------------------------------------
    if (Visited==None):  Visited = [] #開始点のときに探索済みリストをつくる
    Visited.append(Start)  #現在の頂点を探索済みリストに加える
#    print(Start, "\n", Visited)  #現在の頂点と探索済みリストを表示
#    print(len(Visited))
    if len(Visited)==Vn:
        global ans
        ans=ans+1
    for Next in Graph[Start]:  #次に探索する頂点の候補
        if ( Next in Visited ):  continue  #訪問済みリストにある頂点は飛ばす
        dfs(Graph, Next, Vn, Visited)  #再帰
#--------------------------------------------------------        

for i in range(m) :
    pp[i] =[int(i) for i in input().split()]

for i in range(m) : #辺を１つ削除してその辺が橋かどうかを深さ優先探索で調べる
    p=pp[:]
    del p[i]

#隣接リスト
    l = [[] for i in range(n+1)] #頂点の数+1
    for pi in p:  #0-originedに注意
        l[pi[0]].append(pi[1])
        l[pi[1]].append(pi[0])
#    print(l)
    
    #頂点０（１）から探索
    dfs(l, 0, n)
#    print(ans)
print((m-ans))

# Optimized solution
n,m=[int(i) for i in input().split()]
pp=[[0 for i in range(2)] for j in range(m)]
not_bridge=0

#深さ優先探索-------------------------------------------------------------
#参照元：Python3でグラフの実装＆深さ優先探索・幅優先探索の実装
#http://muromura.hatenablog.com/entry/2017/07/16/025414
def dfs(Graph, Start, Vn, Visited=None):
  
##追加（空から読んでも動くように修正）-----------------------
  if Graph[Start]==[] and Visited==None: 
        Next=Start+1        
        dfs(Graph, Next, Vn, Visited)  #再帰
  else:
##--------------------------------------------------------

    if (Visited==None):  Visited = [] #開始点のときに探索済みリストをつくる
    Visited.append(Start)  #現在の頂点を探索済みリストに加える
#    print(Start, "\n", Visited)  #現在の頂点と探索済みリストを表示
    if len(Visited)==Vn: #全点訪問済みの場合、橋ではない
        global not_bridge
        not_bridge=not_bridge+1
    for Next in Graph[Start]:  #次に探索する頂点の候補
        if ( Next in Visited ):  continue  #訪問済みリストにある頂点は飛ばす
        dfs(Graph, Next, Vn, Visited)  #再帰
#-----------------------------------------------------------------------        

for i in range(m) :
    pp[i] =[int(i) for i in input().split()]

for i in range(m) : #辺を１つ削除してその辺が橋かどうかを深さ優先探索で調べる
    p=pp[:]
    del p[i]

#隣接リスト
    l = [[] for i in range(n+1)] #頂点の数+1
    for pi in p:  #0-originedに注意、l[0]は空
        l[pi[0]].append(pi[1])
        l[pi[1]].append(pi[0])

    
    #頂点０（１）から探索
    dfs(l, 0, n)
    
print((m-not_bridge))
# Original 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 unite(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 size(self, x):
        return -self.parents[self.find(x)]

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

n, m = list(map(int, input().split()))
edge = []
for i in range(m):
    a, b = list(map(int, input().split()))
    edge.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(edge[j][0], edge[j][1])
    ans += (len(uf.roots()) != 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 unite(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 size(self, x):
        return -self.parents[self.find(x)]

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

n, m = list(map(int, input().split()))
edge = []
for i in range(m):
    a, b = list(map(int, input().split()))
    edge.append([a - 1, b - 1])
ans = 0
for i in range(m):
    uf = UnionFind(n)
    for j in range(m):
        if i != j: uf.unite(edge[j][0], edge[j][1])
    ans += not uf.same(edge[i][0], edge[i][1])
print(ans)
# Original solution
import copy

def j(n):
    if n:print("Yes")
    else:print("No")
    exit(0)
rem = 10 ** 9 + 7

"""
def ct(x,y):
    if (x>y):print("+")
    elif (x<y): print("-")
    else: print("?")
"""

def ip():
    return int(eval(input()))
def iprow():
    return [int(i) for i in input().split()]
def ips():
    return (int(i) for i in input().split())
def ipmultiplerow(n):
    a = []
    for i in range(n):
        a.append(ip())
    return a
def printrow(a):
    for i in a:
        print(i)

n,m = ips()
a = []
s = 0
for i in range(m):
    x,y = ips()
    a.append([x,y])
connection = [[] for i in range(n+1)]
for i in range(m):
    [x,y] = a[i]
    connection[x].append(y)
    connection[y].append(x)
for loop in range(100):
    for i in range(1,n+1):
        if len(connection[i]) == 1:
            temp = connection[i][0]
            d = connection[temp].index(i)
            connection[temp].pop(d)
            s+=1
            connection[i].pop(0)
print(s)

# Optimized solution
import copy

def j(n):
    if n:print("Yes")
    else:print("No")
    exit(0)
rem = 10 ** 9 + 7

"""
def ct(x,y):
    if (x>y):print("+")
    elif (x<y): print("-")
    else: print("?")
"""

def ip():
    return int(eval(input()))
def iprow():
    return [int(i) for i in input().split()]
def ips():
    return (int(i) for i in input().split())
def ipmultiplerow(n):
    a = []
    for i in range(n):
        a.append(ip())
    return a
def printrow(a):
    for i in a:
        print(i)

n,m = ips()
a = []
s = 0
for i in range(m):
    x,y = ips()
    a.append([x,y])
connection = [[] for i in range(n+1)]
for i in range(m):
    [x,y] = a[i]
    connection[x].append(y)
    connection[y].append(x)
for loop in range(50):
    for i in range(1,n+1):
        if len(connection[i]) == 1:
            temp = connection[i][0]
            d = connection[temp].index(i)
            connection[temp].pop(d)
            s+=1
            connection[i].pop(0)
print(s)
# Original solution
import sys
from collections import deque

input = sys.stdin.readline


def dfs(G, v, visited):
    visited[v] = True
    stack = deque([v])
    while stack:
        v = stack.pop()
        for u in G[v]:
            if visited[u]:
                continue
            visited[u] = True
            stack.append(u)


def main():
    N, M = list(map(int, input().split()))
    G = [[] for _ in range(N)]
    a = [0] * M
    b = [0] * M
    for i in range(M):
        a[i], b[i] = list(map(int, input().split()))
        a[i] -= 1
        b[i] -= 1
        G[a[i]].append(b[i])
        G[b[i]].append(a[i])

    ans = 0
    for i in range(M):
        G[a[i]].remove(b[i])
        G[b[i]].remove(a[i])

        visited = [False] * N
        dfs(G, 0, visited)

        if not all(visited):
            ans += 1

        G[a[i]].append(b[i])
        G[b[i]].append(a[i])

    print(ans)


if __name__ == "__main__":
    main()


# Optimized solution
import sys
input = sys.stdin.readline  # NOQA
sys.setrecursionlimit(10 ** 7)  # NOQA


def dfs(G, v, visited):
    """G: graph, v: vertex"""
    visited[v] = True
    for c in G[v]:
        if visited[c]:
            continue
        dfs(G, c, visited)


def main():
    N, M = list(map(int, input().split()))
    a = [None] * M
    b = [None] * M
    G = [[] for _ in range(N)]
    for i in range(M):
        a[i], b[i] = list(map(int, input().split()))
        a[i] -= 1
        b[i] -= 1
        G[a[i]].append(b[i])
        G[b[i]].append(a[i])

    ans = 0
    for i in range(M):
        # remove an adge
        G[a[i]].remove(b[i])
        G[b[i]].remove(a[i])

        visited = [False] * N
        visited[0] = True
        dfs(G, 0, visited)

        if visited.count(True) != N:
            ans += 1

        # restore an edge
        G[a[i]].append(b[i])
        G[b[i]].append(a[i])

    print(ans)


if __name__ == "__main__":
    main()
# Original solution
n, m = list(map(int, input().split()))
ab = [list(map(int, input().split())) for _ in range(m)]

def bfs(i, edges, r):
    if all(r):
        return True
    if r[i]:
        return False
    r[i] = True
    if any([bfs(e, edges, r) for e in edges[i]]):
        return True
    return False

ans = 0
for i in range(m):
    edges = [[] for _ in range(n)]
    for j in range(m):
        if i != j:
            edges[ab[j][0]-1].append(ab[j][1]-1)
            edges[ab[j][1]-1].append(ab[j][0]-1)
    reached = [False for _ in range(n)]
    if not bfs(0, edges, reached):
        ans += 1
print(ans)

# Optimized solution
def dfs(i,edge):
    if vis[i]==True:
        return
    vis[i]=True
    for j in edge[i]:
        dfs(j,edge)

N,M=list(map(int,input().split()))
ab=[[0,0] for _ in range(M)]
for i in range(M):
    a,b=list(map(int,input().split()))
    ab[i][0],ab[i][1]=a,b
ans=0
for i in range(M):
    edge=[[] for _ in range(N+1)]
    for j in range(M):
        if j!=i:
            edge[ab[j][0]].append(ab[j][1])
            edge[ab[j][1]].append(ab[j][0])
    vis=[False]*(N+1)
    vis[0]=True
    dfs(1,edge)
    if False in vis:
        ans+=1
print(ans)
# Original solution
n, m = list(map(int, input().split()))

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


class Union:
    def __init__(self, val):
        self.val = val
        self.parent = self
        self.rank = 0

    def get_root(self):
        n = self
        while n.parent != n:
            n = n.parent
        return n

    def find_set(self):
        return self.get_root().val

ans = 0
for i in range(m):
    unions = []
    for val in range(n):
        unions.append(Union(val))
    for index, (a, b) in enumerate(brides):
        if i == index:
            continue
        x, y = unions[a - 1], unions[b - 1]
        x_root = x.get_root()
        y_root = y.get_root()
        if x_root.rank == y_root.rank:
            x_root.rank += 1
            y_root.parent = x_root
        elif x_root.rank > y_root.rank:
            y_root.parent = x_root
        else:
            x_root.parent = y_root

    groups = [None] * n
    for j, u in enumerate(unions):
        root = u.find_set()
        groups[j] = root
    for g in range(1, n):
        if groups[g - 1] != groups[g]:
            ans += 1
            break
print(ans)

# Optimized solution
# https://atcoder.jp/contests/abc075/tasks/abc075_c
# 各頂点を結ぶクエリー(a, bの情報)ごとに、
# それを実行しなくてもたどり着ける頂点が総頂点数(n)に一致するかを判定する。
n, m = list(map(int, input().split()))

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

def dfs(graph, n):
    visited = [False] * (n + 1)
    visited[0] = True # 1-indexedなので、0番目は関係ない
    stack = [1]
    num = 0
    while stack:
        t = stack.pop()
        for node in graph[t]:
            if visited[node]:
                continue
            visited[node] = True
            stack.append(node)
            num += 1
    if num == n:
        return True
    return False

ans = 0
for i in range(m):
    graph = [[] for _ in range(n + 1)]
    for j in range(m):
        if i == j:
            continue
        a, b = queries[j]
        graph[a].append(b)
        graph[b].append(a)
    if not dfs(graph, n):
        ans += 1
print(ans)
# Original solution
import math,string,itertools,fractions,heapq,collections,re,array,bisect,sys,random,time,queue,copy

sys.setrecursionlimit(10**7)
inf=10**20
mod=10**9+7
dd=[(-1,0),(0,1),(1,0),(0,-1)]
ddn=[(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1),(-1,-1)]

def LI(): return [int(x) for x in sys.stdin.readline().split()]
def I(): return int(sys.stdin.readline())
def LS(): return sys.stdin.readline().split()
def S(): return eval(input())

nmax=50
graph=[[False]*nmax for _ in range(nmax)]
visited=[False]*nmax

def dfs(n,v):
  visited[v]=True
  for v2 in range(n):
    if graph[v][v2]==False:
      continue
    if visited[v2]:
      continue
    dfs(n,v2)

def main():
  n,m=LI()
  a=[]
  b=[]
  for _ in range(m):
    _a,_b=LI()
    a.append(_a-1)
    b.append(_b-1)
    graph[_a-1][_b-1]=True
    graph[_b-1][_a-1]=True

  ans=0
  for i in range(m):
    graph[a[i]][b[i]]=False
    graph[b[i]][a[i]]=False

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

    dfs(n,j)

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

    graph[a[i]][b[i]]=True
    graph[b[i]][a[i]]=True

  return ans

# main()
print((main()))


# Optimized solution
import math,itertools,fractions,heapq,collections,bisect,sys,queue,copy

sys.setrecursionlimit(10**7)
inf=10**20
mod=10**9+7
dd=[(-1,0),(0,1),(1,0),(0,-1)]
ddn=[(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1),(-1,-1)]

def LI(): return [int(x) for x in sys.stdin.readline().split()]
def LF(): return [float(x) for x in sys.stdin.readline().split()]
def I(): return int(sys.stdin.readline())
def F(): return float(sys.stdin.readline())
def LS(): return sys.stdin.readline().split()
def S(): return eval(input())

limit=50
graph=[[False]*limit for _ in range(limit)]
visited=[False]*limit

def dfs(n,v):
  visited[v]=True
  for v2 in range(n):
    if graph[v][v2]==False:
      continue
    if visited[v2]:
      continue
    dfs(n,v2)

def main():
  n,m=LI()
  a=[[] for _ in range(n*n)]
  b=[[] for _ in range(n*n)]
  for i in range(m):
    _a,_b=LI()
    _a-=1
    _b-=1
    a[i]=_a
    b[i]=_b
    graph[_a][_b]=True
    graph[_b][_a]=True

  ans=0
  for i in range(m):
    graph[a[i]][b[i]]=False
    graph[b[i]][a[i]]=False

    for j in range(n):
      visited[j]=False
    dfs(n,0)
    no_visited=False
    for j in range(n):
      if visited[j]==False:
        no_visited=True
    if no_visited:
      ans+=1
    graph[a[i]][b[i]]=True
    graph[b[i]][a[i]]=True

  return ans

# main()
print((main()))
# Original solution
n,m=list(map(int, input().split()))
ab=[list(map(int,input().split())) for i in range(m)]

from pprint import pprint

l=[[False for _ in range(n)] for _ in range(n)]
v=[True for _ in range(n)]
for a,b in ab:
    l[b-1][a-1], l[a-1][b-1]=True,True

def dfs(x,t,v,j,m,n):
    j+=1
    if j<=m:
        v[x]=False
        for i in range(1,n):
            if t[x][i]:
                dfs(i,t,v,j,m,n)
import copy

ans=0
for i in range(m):
    j=0
    t=copy.deepcopy(l)
    t[ab[i][1]-1][ab[i][0]-1],t[ab[i][0]-1][ab[i][1]-1]=False,False
    v=[True for _ in range(n)]
    if i!=0:
        dfs(ab[0][0]-1, t, v, j, m,n)
    else:
        dfs(ab[1][0]-1, t, v, j, m,n)
        
    z=False
    for x in v:
        if x:
            z=True
    if z:
        ans+=1
print(ans)

# Optimized solution
n,m=list(map(int, input().split()))
ab=[list(map(int,input().split())) for i in range(m)]

from pprint import pprint

l=[[False for _ in range(n)] for _ in range(n)]
v=[True for _ in range(n)]
for a,b in ab:
    l[b-1][a-1], l[a-1][b-1]=True,True
def dfs(x,t,v,j,m,n):
    j+=1
    if j<n+m:
        v[x]=False
        for i in range(1,n):
            if v[i]:
                if t[x][i]:
                    dfs(i,t,v,j,m,n)
import copy

ans=0
for i in range(m):
    j=0
    t=copy.deepcopy(l)
    t[ab[i][1]-1][ab[i][0]-1],t[ab[i][0]-1][ab[i][1]-1]=False,False
    v=[True for _ in range(n)]
    dfs(0, t, v, j, m,n)
        
    z=False
    for x in v:
        if x:
            z=True
    if z:
        ans+=1
print(ans)