Problem:
Score: 600 points

Problem Statement

M-kun is a brilliant air traffic controller.

On the display of his radar, there are N airplanes numbered 1, 2, ..., N, all flying at the same altitude.

Each of the airplanes flies at a constant speed of 0.1 per second in a constant direction. The current coordinates of the airplane numbered i are (X_i, Y_i), and the direction of the airplane is as follows:

if U_i is U, it flies in the positive y direction;

if U_i is R, it flies in the positive x direction;

if U_i is D, it flies in the negative y direction;

if U_i is L, it flies in the negative x direction.

To help M-kun in his work, determine whether there is a pair of airplanes that will collide with each other if they keep flying as they are now.

If there is such a pair, find the number of seconds after which the first collision will happen.

We assume that the airplanes are negligibly small so that two airplanes only collide when they reach the same coordinates simultaneously.

Constraints

1 \leq N \leq 200000

0 \leq X_i, Y_i \leq 200000

U_i is U, R, D, or L.

The current positions of the N airplanes, (X_i, Y_i), are all distinct.

N, X_i, and Y_i are integers.

Input

Input is given from Standard Input in the following format:

N
X_1 Y_1 U_1
X_2 Y_2 U_2
X_3 Y_3 U_3
:
X_N Y_N U_N

Output

If there is a pair of airplanes that will collide with each other if they keep flying as they are now, print an integer representing the number of seconds after which the first collision will happen.

If there is no such pair, print SAFE.

Sample Input 1

2
11 1 U
11 47 D

Sample Output 1

230

If the airplanes keep flying as they are now, two airplanes numbered 1 and 2 will reach the coordinates (11, 24) simultaneously and collide.

Sample Input 2

4
20 30 U
30 20 R
20 10 D
10 20 L

Sample Output 2

SAFE

No pair of airplanes will collide.

Sample Input 3

8
168 224 U
130 175 R
111 198 D
121 188 L
201 116 U
112 121 R
145 239 D
185 107 L

Sample Output 3

100

Solution in PyPy3 (7.3.0) (judge verdict: Wrong Answer)

def generate_test_case():
    import random
    random.seed(0)
    ret = list()
    n = 10
    for _ in range(5):
        plane_list = list()
        for _ in range(n):
            x, y = random.choices(range(200001), k=2)
            u = random.choice(['U', 'R', 'D', 'L'])
            plane_list.append((x, y, u))
        ret.append({
            'n': n,
            'plane_list': plane_list
        })
    return ret


def solve_simply(N, plane_list):
    # 1単位時間（5秒）ごとに衝突判定：O(N * max(X_1, Y_1, ..., X_N, Y_N))
    from collections import defaultdict
    occur_collision = False
    LIMIT_T = 200000 * 2
    t = 1
    while t <= LIMIT_T:
        location = defaultdict(int)
        same_location = False
        for plane in plane_list:
            x, y, u = plane
            x *= 2  # 移動単位が0.5なので2倍して格子点上に並べる
            y *= 2
            if u == 'U':
                y += t  # 座標はxy方向に2倍されているので速度は単位時間あたり1。
            elif u == 'R':
                x += t
            elif u == 'D':
                y -= t
            else:
                x -= t
            if location[(x, y)] > 0:
                same_location = True
                break
            location[(x, y)] += 1
        if same_location:
            occur_collision = True
            break
        t += 1
    if occur_collision:
        return t * 5
    else:
        return 'SAFE'


def get_min_t_on_same_line(N, plane_list, t_inf=2*(10**6)):
    import bisect
    from collections import defaultdict
    uy, dy = defaultdict(list), defaultdict(list)
    plane_list.sort(key=lambda p: (p[0], p[1]))  # sort by (x, y)
    for plane in plane_list:
        x, y, u = plane
        if u == 'U':
            uy[x].append(y)
        elif u == 'D':
            dy[x].append(y)
    lx, rx = defaultdict(list), defaultdict(list)
    plane_list.sort(key=lambda p: (p[1], p[0]))  # sort by (y, x)
    for plane in plane_list:
        x, y, u = plane
        if u == 'R':
            rx[y].append(x)
        elif u == 'L':
            lx[y].append(x)
    min_t = t_inf
    for y in rx.keys():  # 右向きの飛行機の対になる左向き飛行機の探索
        for x in rx[y]:
            index = bisect.bisect_right(lx[y], x)
            if index >= len(lx[y]):
                continue
            dist = lx[y][index] - x
            min_t = min(min_t, 5 * dist)
    for x in uy.keys():  # 上向きの飛行機の対になる下向き飛行機の探索
        for y in uy[x]:
            index = bisect.bisect_right(dy[x], y)
            if index >= len(dy[x]):
                continue
            dist = dy[x][index] - y
            min_t = min(min_t, 5 * dist)
    return min_t


def get_min_t_at_crossing(N, plane_list, t_inf=2*(10**6)):
    from collections import defaultdict
    import bisect
    plane_list.sort(key=lambda p: p[0])  # sort by x
    min_t = t_inf
    # (1) RU / LDの衝突：点をy+x=kのkで分類
    rx, ux = defaultdict(list), defaultdict(list)
    lx, dx = defaultdict(list), defaultdict(list)
    for plane in plane_list:
        x, y, u = plane
        k = y + x
        if u == 'U':
            ux[k].append(x)
        elif u == 'R':
            rx[k].append(x)
        elif u == 'D':
            dx[k].append(x)
        else:
            lx[k].append(x)
    for k in rx.keys():  # 右向きの飛行機の対になる上向き飛行機の探索
        for x in rx[k]:
            index = bisect.bisect_right(ux[k], x)
            if index >= len(ux[k]):
                continue
            x_diff = ux[k][index] - x
            min_t = min(min_t, 10 * x_diff)
    for k in dx.keys():  # 下向きの飛行機の対になる左向き飛行機の探索
        for x in dx[k]:
            index = bisect.bisect_right(lx[k], x)
            if index >= len(lx[k]):
                continue
            x_diff = lx[k][index] - x
            min_t = min(min_t, 10 * x_diff)
    # (2) RD / LUの衝突：点をy-x=kのkで分類
    rx, ux = defaultdict(list), defaultdict(list)
    lx, dx = defaultdict(list), defaultdict(list)
    for plane in plane_list:
        x, y, u = plane
        k = y - x
        if u == 'U':
            ux[k].append(x)
        elif u == 'R':
            rx[k].append(x)
        elif u == 'D':
            dx[k].append(x)
        else:
            lx[k].append(x)
    for k in rx.keys():  # 右向きの飛行機の対になる下向き飛行機の探索
        for x in rx[k]:
            index = bisect.bisect_right(dx[k], x)
            if index >= len(dx[k]):
                continue
            x_diff = dx[k][index] - x
            min_t = min(min_t, 10 * x_diff)
    for k in ux.keys():  # 上向きの飛行機の対になる左向き飛行機の探索
        for x in ux[k]:
            index = bisect.bisect_right(lx[k], x)
            if index >= len(lx[k]):
                continue
            x_diff = lx[k][index] - x
            min_t = min(min_t, 10 * x_diff)
    return min_t


def solve(N, plane_list):
    T_INF = 2 * (10 ** 6)
    # 同一直線上での衝突の探索
    min_t = get_min_t_on_same_line(N, plane_list, T_INF)
    # クロス衝突の探索
    min_t = min(min_t, get_min_t_at_crossing(N, plane_list, T_INF))
    if min_t == T_INF:
        return 'SAFE'
    return min_t


def main():
    N = int(input())
    plane_list = list()
    for _ in range(N):
        X, Y, U = input().split()
        X, Y = int(X), int(Y)
        plane_list.append((X, Y, U))
    print(solve(N, plane_list))


def check():
    for i, test_case in enumerate(generate_test_case()):
        print('Check {}-th test case'.format(i))
        N, plane_list = test_case['n'], test_case['plane_list']
        assert solve_simply(N, plane_list) == solve(N, plane_list)
    print('OK')


if __name__ == '__main__':
    main()
    # check()