use std::io::stdin;
use std::hash::Hash;
use std::collections::BinaryHeap;
use std::collections::HashSet;
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::cmp::Ordering;

fn main(){
    let startgoal: Vec<i64> = getvec();
    let (start, goal) = ((startgoal[0], startgoal[1]), (startgoal[2], startgoal[3]));
    let n: usize = getvec()[0];
    let mut xyrs = Vec::new();

    xyrs.push((start.0, start.1, 0.));
    xyrs.push((goal.0, goal.1, 0.));

    let mut graph: Graph<(i64, i64)> = Graph::new();

    graph.entry(start)
        .or_insert_with(BTreeMap::new)
        .insert(goal, diff(&start, &goal));

    for _ in 0..n {
        let xyr: Vec<i64> = getvec();
        let (x, y, r_i) = (xyr[0], xyr[1], xyr[2]);
        let r = r_i as f64;

        for &(x2, y2, r2) in xyrs.iter() {
            let cost = max(diff(&(x, y), &(x2, y2)) - r - r2, 0.);
            graph.entry((x, y))
                .or_insert_with(BTreeMap::new)
                .insert((x2, y2), cost);
            graph.entry((x2, y2))
                .or_insert_with(BTreeMap::new)
                .insert((x, y), cost);
        }

        xyrs.push((x, y, r));
    }
    if let Some(ans) = dijkstra(&graph, start, goal) {
        println!("{}", ans)
    }
}

fn diff(u: &(i64, i64), v: &(i64, i64)) -> f64 {
    (((u.0 - v.0).pow(2)  + (u.1 - v.1).pow(2)) as f64).sqrt()
}

#[allow(dead_code)]
fn getline() -> String {
    let mut s = String::new();
    match stdin().read_line(&mut s){
        Ok(_) => {s.trim().to_string()}
        Err(_) => String::new()
    }
}

#[allow(dead_code)]
fn getvec<T: std::str::FromStr>() -> Vec<T>{
    let line = getline();
    line.split_whitespace().filter_map(|x| x.parse().ok()).collect()
}

#[allow(dead_code)]
fn getpair() -> (usize, usize){
    let nm = getvec();
    (nm[0], nm[1])
}

type Graph<N> = HashMap<N, BTreeMap<N, f64>>;

#[derive(PartialEq)]
struct RevOrd<M: PartialOrd>(M);

#[allow(dead_code)]
impl <M: PartialOrd> PartialOrd for RevOrd<M> {
    fn partial_cmp(&self, other: &RevOrd<M>) -> Option<Ordering> {
        other.0.partial_cmp(&self.0)
    }
}

#[allow(dead_code)]
impl <M: PartialOrd> Eq for RevOrd<M> {}

#[allow(dead_code)]
impl <M: PartialOrd> Ord for RevOrd<M> {
    fn cmp(&self, other: &RevOrd<M>) -> Ordering {
        match other.0.partial_cmp(&self.0) {
            Some(x) => x,
            None => Ordering::Equal
        }
    }
}

#[allow(dead_code)]
struct MinHeap<M: PartialOrd>(BinaryHeap<RevOrd<M>>);

#[allow(dead_code)]
impl <M: PartialOrd> MinHeap<M> {
    fn new() -> MinHeap<M> {
        MinHeap(BinaryHeap::new())
    }

    fn push(&mut self, elm: M) {
        self.0.push(RevOrd(elm))
    }

    fn pop(&mut self) -> Option<M> {
        match self.0.pop() {
            Some(RevOrd(elm)) => Some(elm),
            _ => None
        }
    }
}

fn max(x: f64, y: f64) -> f64 {
    if x > y { x } else { y }
}

fn dijkstra<N>(graph: &Graph<N>, start: N, end: N) -> Option<f64>
    where N: Eq + Ord + Hash + Copy
{
    let mut heap = MinHeap::new();
    let mut visited = HashSet::new();

    heap.push((0., start));

    while let Some((cost, current)) = heap.pop() {
        if visited.contains(&current) { continue; }
        if current == end { return Some(cost); }
        visited.insert(current);
        if let Some(edges) = graph.get(&current) {
            for (next, path_cost) in edges {
                heap.push((cost + *path_cost, next.clone()))
            }
        }
    }
    None
}

Measured memory_bytes: 77780