use::std::vec::Vec;
use::std::collections::VecDeque;

fn read<T: std::str::FromStr>() -> T {
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).ok();
    s.trim().parse().ok().unwrap()
}

fn read_vec<T: std::str::FromStr>() -> Vec<T> {
    read::<String>().split_whitespace().map(|e| e.parse().ok().unwrap()).collect()
}

fn main() {
    let (n, m) = {
      let v: Vec<usize> = read_vec();
      (v[0], v[1])
    };
    let mut brigde: Vec<(usize, usize)> = Vec::new();
    for _ in 0..m {
      let b: (usize, usize) = {
        let v: Vec<usize> = read_vec();
        (v[0] - 1, v[1] - 1)
      };
      brigde.push(b);
    }

    let mut g: Graph = Graph::new(n, &brigde, false);
    let mut result: u64 = 0;

    for &(i, j) in &brigde {
      g.graph[i][j] = -1;
      g.graph[j][i] = -1;
      if n != g.bfs(0, 0, calc) as usize {
        result += 1;
      }
      g.graph[i][j] = 1;
      g.graph[j][i] = 1;
    }

    println!("{}", result);
}

fn calc(result: &mut u64, i: usize, j: usize) {
    *result += 1;
}



#[derive(Debug, Clone)]
struct Graph {
  n: usize,                            // number of nodes
  graph: Vec<Vec<i64>>,              // first: from node, second: to node, third: cost
}

impl Graph {
  // constructor
  fn new(n: usize, v: &Vec<(usize, usize)>, direct: bool) -> Graph {
    let mut g: Vec<Vec<i64>>  = vec![vec![-1; n]; n];
    for &(from, to) in v {
      g[from][to] = 1;
      if !direct {
        g[to][from] = 1;
      }
    }

    Graph { n: n, graph: g, }
  }

  // breadth first search
  fn bfs<R: std::fmt::Debug>(&self, start: usize, init: R, f: fn(&mut R, usize, usize)) -> R {
    let mut result: R = init;                          // result variable
    let mut pre_node: usize;
    let mut visit: Vec<bool> = vec![false; self.n];    // record already visit to node
    let mut q: VecDeque<usize> = VecDeque::new();

    // init
    pre_node = start;
    visit[start] = true;
    q.push_back(start);


    while !q.is_empty() {
      let v: usize = match q.pop_front() {
        None => panic!("unifined"),
        Some(i) => i,
      };

      // caluclate
      f(&mut result, pre_node, v);
      pre_node = v;

      for i in 0..self.n {
        if !visit[i] && self.graph[v][i] > 0 {
          q.push_back(i);
          visit[i] = true;  // already visit node v, so mark
        }
      }
    }
    return result;
  }

  // depth first search
  fn dfs<R>(&self, start: usize, init: R, f: fn(&mut R, usize, usize)) -> R {
    let mut result: R = init;                          // result variable
    let mut pre_node: usize;
    let mut visit: Vec<bool> = vec![false; self.n];    // record already visit to node
    let mut stack: Vec<usize> = Vec::new();

    // init
    pre_node = start;
    visit[start] = true;
    stack.push(start);


    while !stack.is_empty() {
      let v: usize = match stack.pop() {
        None => panic!("unifined"),
        Some(i) => i,
      };

      // caluclate
      f(&mut result, pre_node, v);
      pre_node = v;

      for i in 0..self.n {
        if !visit[i] && self.graph[v][i] > 0 {
          stack.push(i);
          visit[i] = true;  // already visit node v, so mark
        }
      }
    }
    return result;
  }

}


Measured memory_bytes: 4352