use std::io::*;
use std::str::FromStr;
use std::collections::VecDeque;

#[allow(dead_code)]
fn read<T: FromStr>() -> T {
    let stdin = stdin();
    let stdin = stdin.lock();
    let token: String = stdin
        .bytes()
        .map(|c| c.expect("failed to read char") as char)
        .skip_while(|c| c.is_whitespace())
        .take_while(|c| !c.is_whitespace())
        .collect();
    token.parse().ok().expect("failed to parse token")
}

#[allow(dead_code)]
struct FenwickTree {
    seq: Vec<i64>,
}
#[allow(dead_code)]
impl FenwickTree {
    fn new(n: usize) -> FenwickTree {
        FenwickTree { seq: vec![0; n + 2] }
    }
    fn add(&mut self, index: usize, value: i64) {
        let mut index = (index + 1) as i32;
        while (index as usize) < self.seq.len() {
            self.seq[index as usize] += value;
            index += index & -index;
        }
    }
    // inclusive
    fn sum(&self, index: usize) -> i64 {
        let mut ret = 0;
        let mut index = (index + 1) as i32;
        while index > 0 {
            ret += self.seq[index as usize];
            index -= index & -index;
        }
        ret
    }
}

type Weight = i32;
#[allow(dead_code)]
#[derive(Clone, Debug, Eq, PartialEq)]
struct Edge {
    index: usize,
    src: usize,
    dest: usize,
    weight: Weight,
}
#[allow(dead_code)]
impl Edge {
    fn new(index: usize, src: usize, dest: usize, weight: Weight) -> Edge {
        Edge {
            index: index,
            src: src,
            dest: dest,
            weight: weight,
        }
    }
}
impl std::cmp::Ord for Edge {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        (self.weight, self.src, self.dest).cmp(&(other.weight, other.src, other.dest))
    }
}
impl std::cmp::PartialOrd for Edge {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}
type Edges = Vec<Edge>;
type Graph = Vec<Edges>;
#[allow(dead_code)]
fn graph_new(n: usize) -> Graph {
    vec![vec![]; n]
}
#[allow(dead_code)]
fn add_bi_edge(graph: &mut Graph, index: &mut usize, src: usize, dest: usize, weight: Weight) {
    graph[src].push(Edge::new(*index, src, dest, weight));
    graph[dest].push(Edge::new(*index, dest, src, weight));
    *index += 1;
}
#[allow(dead_code)]
fn add_uni_edge(graph: &mut Graph, index: &mut usize, src: usize, dest: usize, weight: Weight) {
    graph[src].push(Edge::new(*index, src, dest, weight));
    *index += 1;
}



struct Solver {
    n: usize,
    g: Graph,
    edge_cnt: Vec<usize>,
}

impl Solver {
    fn new() -> Solver {
        Solver {
            n: 0,
            g: vec![],
            edge_cnt: vec![],
        }
    }
    fn init(&mut self) {
        let n: usize = read();
        self.n = n;
        self.g = graph_new(n);
        self.edge_cnt = vec![usize::max_value(); n - 1];
        let mut e = 0;
        for _ in 0..n-1 {
            let mut a: usize = read();
            let mut b: usize = read();
            a -= 1; b -= 1;
            add_bi_edge(&mut self.g, &mut e, a, b, 0);
        }
    }
    fn bfs(&self, len: usize) -> i32 {
        let n = self.n;
        let mut childs = vec![vec![]; n];
        let mut used = vec![false; n - 1];
        let mut ret = 0;
        let mut que = VecDeque::new();
        for i in 0..n {
            if self.g[i].len() == 1 { que.push_back(i); }
        }
        while que.front().is_some() {
            let from = *que.front().unwrap();
            que.pop_front();
            let mut rest = 0;
            {
                childs[from].sort();
                childs[from].reverse();
                let childs = childs[from].clone();
                let mut used = vec![false; childs.len()];
                let mut front = 0;
                let mut other = vec![];
                for back in (0..childs.len()).rev() {
                    if used[back] { continue; }
                    while front < back && childs[back] + childs[front] >= len {
                        other.push(front);
                        front += 1;
                    }
                    while !other.is_empty() && *other.last().unwrap() >= back { other.pop(); }
                    if !other.is_empty() {
                        let index = *other.last().unwrap();
                        assert!(childs[index] + childs[back] >= len);
                        assert!(index != back);
                        other.pop();
                        used[back] = true;
                        used[index] = true;
                        ret -= 1;
                    }
                }
                for i in 0..childs.len() {
                    if !used[i] {
                        rest = childs[i];
                        break;
                    }
                }
            }
            for edge in self.g[from].clone().iter() {
                if used[edge.index] { continue; }
                used[edge.index] = true;
                if rest == 0 {
                    rest = len;
                    ret += 1;
                }
                rest -= 1;
                childs[edge.dest].push(rest);
                // println!("{} {:?}", edge.dest, self.g[edge.dest]);
                if childs[edge.dest].len() == self.g[edge.dest].len() - 1 {
                    que.push_back(edge.dest);
                }
            }
        }
        return ret;
    }
    fn solve(&mut self) {
        let n = self.n;
        let e = n - 1;
        let cnt = self.bfs(e);
        let mut left = 0;
        let mut right = e;
        while right - left > 1 {
            let mid = (left + right) / 2;
            if self.bfs(mid) == cnt {
                right = mid;
            } else {
                left = mid;
            }
        }
        println!("{} {}", cnt, right);
    }
}

fn main() {
    let mut solver = Solver::new();
    solver.init();
    solver.solve();
}

Measured memory_bytes: 40212