import java.util.*

fun main(args: Array<String>) {
    val (N, M) = readLine()!!.split(" ").map {it.toInt()}
    val g = Graph(N + 1, 0, 0) { a, b -> a + b }

    for (i in 1 .. M) {
        val (a, b) = readLine()!!.split(" ").map{it.toInt()}
        g.edges[a].add(Edge(a, b, 0))
        g.edges[b].add(Edge(b, a, 0))
    }

    println(findBridges(g).first.size)
}

data class Edge<T: Comparable<T>>(val from: Int, val to: Int, val weight: T): Comparable<Edge<T>> {
    constructor(from: Long, to: Long, weight: T) : this(from.toInt(), to.toInt(), weight)

    override fun compareTo(other: Edge<T>) = weight.compareTo(other.weight)
}

class Graph<T: Comparable<T>>(val n: Int, val zero: T, val inf: T, val plus: (T, T) -> T) {
    val edges = Array(n) { ArrayList<Edge<T>>() }
}

fun <T : Comparable<T>>findBridges(g: Graph<T>): Pair<Array<Edge<T>>, Array<TreeSet<Int>>> {
    val num = Array(g.n) {0}
    val open = Array(g.n) {false}
    val oReps = ArrayDeque<Int>()
    val oNodes = ArrayDeque<Int>()
    var cnt = 0
    val bridges = ArrayList<Edge<T>>()
    val comp = ArrayList<TreeSet<Int>>()

    fun recur(v: Int, u: Int) {
        if (num[v] != 0) {
            if (open[v]) while(num[oReps.first] > num[v]) oReps.removeFirst()
            return
        }

        num[v] = ++cnt
        oReps.addFirst(v)
        oNodes.addFirst(v)
        open[v] = true

        for (e in g.edges[v]) if (e.to != u) {
            recur(e.to, e.from)
        }

        if (v == oReps.first) {
            oReps.removeFirst()
            comp.add(TreeSet())

            do {
                val w = oNodes.first
                oNodes.removeFirst()
                open[w] = false
                comp.last().add(w)
            } while (v != w)
            bridges.add(Edge(Math.min(u, v), Math.max(u, v), g.zero))
        }
    }

    for (i in 0 until g.n) if (num[i] == 0) {
        recur(i, g.n)
        bridges.removeAt(bridges.size - 1)
    }

    return Pair(bridges.toTypedArray(), comp.toTypedArray())
}
