#![allow(unused_imports)]
#![allow(non_snake_case)]
use std::cmp::*;
use std::collections::*;
use std::io::Write;

#[allow(unused_macros)]
macro_rules! debug {
    ($($e:expr),*) => {
        #[cfg(debug_assertions)]
        $({
            let (e, mut err) = (stringify!($e), std::io::stderr());
            writeln!(err, "{} = {:?}", e, $e).unwrap()
        })*
    };
}

const x_max: usize = 200001;
// const x_max: usize = 20001;

fn main() {
    let n = read::<usize>();
    let mut u = vec![];
    let mut r = vec![];
    let mut d = vec![];
    let mut l = vec![];
    for i in 0..n {
        let v = read_vec::<String>();
        let x: usize = v[0].parse().unwrap();
        let y: usize = v[1].parse().unwrap();
        if v[2] == "U" {
            u.push((x, y));
        } else if v[2] == "R" {
            r.push((x, y));
        } else if v[2] == "D" {
            d.push((x, y));
        } else {
            l.push((x, y));
        }
    }
    let mut ans = std::usize::MAX;

    ans = min(ans, solve_front(&u, &d));
    let swapped_r = r.iter().map(|&x| (x.1, x.0)).collect::<Vec<_>>();
    let swapped_l = l.iter().map(|&x| (x.1, x.0)).collect::<Vec<_>>();
    ans = min(ans, solve_front(&swapped_r, &swapped_l));

    ans = min(ans, solve_side(&u, &r));

    let x_mirrored_l = l
        .iter()
        .map(|&x| (x_max - 1 - x.0, x.1))
        .collect::<Vec<_>>();
    let x_mirrored_u = u
        .iter()
        .map(|&x| (x_max - 1 - x.0, x.1))
        .collect::<Vec<_>>();
    ans = min(ans, solve_side(&x_mirrored_u, &x_mirrored_l));

    let y_mirrored_r = r
        .iter()
        .map(|&x| (x.0, x_max - 1 - x.1))
        .collect::<Vec<_>>();
    let y_mirrored_d = d
        .iter()
        .map(|&x| (x.0, x_max - 1 - x.1))
        .collect::<Vec<_>>();
    ans = min(ans, solve_side(&y_mirrored_d, &y_mirrored_r));

    let xy_mirrored_l = l
        .iter()
        .map(|&x| (x_max - 1 - x.0, x_max - 1 - x.1))
        .collect::<Vec<_>>();
    let xy_mirrored_d = d
        .iter()
        .map(|&x| (x_max - 1 - x.0, x_max - 1 - x.1))
        .collect::<Vec<_>>();
    ans = min(ans, solve_side(&xy_mirrored_d, &xy_mirrored_l));

    if ans == std::usize::MAX {
        println!("SAFE");
        return;
    }
    println!("{}", ans);
}
fn solve_side(u: &Vec<(usize, usize)>, r: &Vec<(usize, usize)>) -> usize {
    let mut ret = std::usize::MAX;
    let mut r_dict = vec![vec![]; x_max * 2];
    for &(x, y) in r {
        r_dict[x + y].push(y);
    }
    for x in 0..x_max * 2 {
        r_dict[x].sort();
    }

    for &(xu, yu) in u {
        let search = r_dict[xu + yu].upper_bound(&yu);
        if search == r_dict[xu + yu].len() {
            continue;
        }
        ret = min(ret, (r_dict[xu + yu][search] - yu) * 10);
    }
    ret
}

fn solve_front(u: &Vec<(usize, usize)>, d: &Vec<(usize, usize)>) -> usize {
    let mut ret = std::usize::MAX;
    let mut d_dict = vec![vec![]; x_max];
    for &(x, y) in d {
        d_dict[x].push(y);
    }
    for x in 0..x_max {
        d_dict[x].sort();
    }

    for &(xu, yu) in u {
        let search = d_dict[xu].upper_bound(&yu);
        if search == d_dict[xu].len() {
            continue;
        }
        ret = min(ret, (d_dict[xu][search] - yu) * 5);
    }
    ret
}

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()
}
use std::cmp::Ordering;

pub trait BinarySearch<T> {
    fn lower_bound(&self, x: &T) -> usize;
    fn upper_bound(&self, x: &T) -> usize;
}

impl<T: Ord> BinarySearch<T> for [T] {
    fn lower_bound(&self, x: &T) -> usize {
        let mut low = 0;
        let mut high = self.len();

        while low != high {
            let mid = (low + high) / 2;
            match self[mid].cmp(x) {
                Ordering::Less => {
                    low = mid + 1;
                }
                Ordering::Equal | Ordering::Greater => {
                    high = mid;
                }
            }
        }
        low
    }

    fn upper_bound(&self, x: &T) -> usize {
        let mut low = 0;
        let mut high = self.len();

        while low != high {
            let mid = (low + high) / 2;
            match self[mid].cmp(x) {
                Ordering::Less | Ordering::Equal => {
                    low = mid + 1;
                }
                Ordering::Greater => {
                    high = mid;
                }
            }
        }
        low
    }
}


Measured memory_bytes: 33076