본문 바로가기

About/Algorithm

[백준 20056] 마법사 상어와 파이어볼(Python 풀이)

https://www.acmicpc.net/problem/20056

 

20056번: 마법사 상어와 파이어볼

첫째 줄에 N, M, K가 주어진다. 둘째 줄부터 M개의 줄에 파이어볼의 정보가 한 줄에 하나씩 주어진다. 파이어볼의 정보는 다섯 정수 ri, ci, mi, si, di로 이루어져 있다. 서로 다른 두 파이어볼의 위치

www.acmicpc.net


해당 문제는 골드4 레벨의 구현 문제입니다.

풀이


0. 입력 및 전역 변수

첫째 줄에 N, M, K가 주어진다.

둘째 줄부터 M개의 줄에 파이어볼의 정보가 한 줄에 하나씩 주어진다. 파이어볼의 정보는 다섯 정수 ri, ci, mi, si, di로 이루어져 있다.

1
2
3
4
5
6
7
8
9
10
from collections import defaultdict
 
N, M, K = map(int, input().split())
fireballs = defaultdict(list)
for i in range(M):
    r, c, m ,s, d = map(int, input().split())
    fireballs[(r-1, c-1)].append((m, s, d)) # fireballs[(r, c)] : [(m, s, d), (m, s, d)...]
 
dy8 = (-1-101110-1# 8방향
dx8 = (01110-1-1-1# 8방향
cs

 

- 파이어볼을 저장할 변수를 defaultdict로 선언하였습니다. defaultdict의 키는 튜플 (r, c), 기본 값은 list로 튜플 (m, s, d) 값을 원소로 가집니다.

- 7번 라인에서 행과 열번호가 1부터 시작하므로 (r-1, c-1) 값을 키로 설정하였습니다.

- 파이어볼 이동 방향을 정할 용도로 dy8, dx8 변수를 사용하였습니다.

1. 파이어볼 이동

모든 파이어볼이 자신의 방향 d로 속력 s칸 만큼 이동한다.

이동하는 중에는 같은 칸에 여러 개의 파이어볼이 있을 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
def move_fireballs():
    # 파이어볼 이동
    global fireballs
    new_fireballs = defaultdict(list) # 이동 후의 파이어볼 정보
    for loc, info_list in fireballs.items():
        sy, sx = loc
        for m, s, d in info_list:
            ny = (sy + dy8[d] * s) % N # d방향으로 s만큼 이동 후 N과 나머지 연산을 해줌
            nx = (sx + dx8[d] * s) % N # d방향으로 s만큼 이동 후 N과 나머지 연산을 해줌
            new_fireballs[(ny, nx)].append((m, s, d))
 
    fireballs = new_fireballs.copy()
cs

 

- 여기서 주의할 점은 격자가 이어져있다는 것입니다.
(격자의 행과 열은 1번부터 N번까지 번호가 매겨져 있고, 1번 행은 N번과 연결되어 있고, 1번 열은 N번 열과 연결되어 있다.)

- 따라서 8~9번 라인에서 y, x 좌표를 d방향으로 s만큼 이동 후 N으로 나머지 연산을 하여 제자리를 찾아가도록 하였습니다.

- 이동 후의 파이어볼 정보를 new_fireballs에 저장한 후 fireballs를 new_fireballs로 교체하였습니다.

2. 좌표가 중복된 파이어볼 처리

이동이 모두 끝난 뒤, 2개 이상의 파이어볼이 있는 칸에서는 다음과 같은 일이 일어난다.

  1. 같은 칸에 있는 파이어볼은 모두 하나로 합쳐진다.
  2. 파이어볼은 4개의 파이어볼로 나누어진다.
  3. 나누어진 파이어볼의 질량, 속력, 방향은 다음과 같다.
    1. 질량은 ⌊(합쳐진 파이어볼 질량의 합)/5⌋이다.
    2. 속력은 ⌊(합쳐진 파이어볼 속력의 합)/(합쳐진 파이어볼의 개수)⌋이다.
    3. 합쳐지는 파이어볼의 방향이 모두 홀수이거나 모두 짝수이면, 방향은 0, 2, 4, 6이 되고, 그렇지 않으면 1, 3, 5, 7이 된다.
  4. 질량이 0인 파이어볼은 소멸되어 없어진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def all_odd_or_even(dirs):
    # 방향이 모두 홀수 혹은 짝수인 경우 True, 아닌 경우 False 반환
    odd_flag, even_flag = FalseFalse
    for d in dirs:
        if d % 2 == 1:
            odd_flag = True # 홀수 발견
        if d % 2 == 0:
            even_flag = True # 짝수 발견
 
    if odd_flag and even_flag: # 홀/짝 모두 발견된 경우 False 반환
        return False
    return True
 
 
def change_duplicate_fireballs():
    # 좌표가 중복된 파이어볼 처리
    global fireballs
    new_fireballs = defaultdict(list) # 처리후의 파이어볼 정보
 
    for loc, info_ilst in fireballs.items():
        if len(info_ilst) == 1# 해당 좌표에 파이어볼이 1개인 경우
            new_fireballs[loc].append(info_ilst[0]) 
            continue
 
        # 파이어볼이 중복된 경우
        sum_m, sum_s, dirs = 00, [] # 질량합, 속도합, 방향리스트
        for m, s, d in info_ilst:
            sum_m += m
            sum_s += s
            dirs.append(d)
        new_m = int(sum_m / 5# 새로운 파이어볼 질량
        if new_m == 0# 질량이 0인 경우 소멸되므로 continue
            continue
        new_s = int(sum_s/len(info_ilst)) # 새로운 파이어볼 속도
        new_dirs = [0246if all_odd_or_even(dirs) else [1357# 새로운 파이어볼 방향(all_odd_or_even() 함수의 결과에 따름)
        for new_d in new_dirs:
            new_fireballs[loc].append((new_m, new_s, new_d)) 
 
    fireballs = new_fireballs.copy()
cs

 

- change_duplicatre_fireballs() 함수에서 중복된 파이어볼을 처리합니다.

- 좌표의 파이어볼 리스트의 원소 개수가 1이 아닌 경우 파이어볼이 변화하게 됩니다.

- all_odd_or_even() 함수는 인자로 받은 방향정보 리스트(dirs)가 모두 홀수 혹은 짝수인지 확인합니다. 모두 홀수 혹은 짝수 인 경우 True를 아닌 경우 False를 반환합니다.

전체 알고리즘 흐름

1
2
3
4
5
6
7
8
9
for _ in range(K): # k번 반복
    move_fireballs()
    change_duplicate_fireballs()
 
result = 0
for loc, info_list in fireballs.items(): # 파이어볼 리스트에서 질량합을 구함
    for m, s, d in info_list:
        result += m
print(result)
cs

전체 소스 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from collections import defaultdict
 
N, M, K = map(int, input().split())
fireballs = defaultdict(list)
for i in range(M):
    r, c, m ,s, d = map(int, input().split())
    fireballs[(r-1, c-1)].append((m, s, d)) # fireballs[(r, c)] : [(m, s, d), (m, s, d)...]
 
dy8 = (-1-101110-1# 8방향
dx8 = (01110-1-1-1# 8방향
 
def move_fireballs():
    # 파이어볼 이동
    global fireballs
    new_fireballs = defaultdict(list) # 이동 후의 파이어볼 정보
    for loc, info_list in fireballs.items():
        sy, sx = loc
        for m, s, d in info_list:
            ny = (sy + dy8[d] * s) % N # d방향으로 s만큼 이동 후 N과 나머지 연산을 해줌
            nx = (sx + dx8[d] * s) % N # d방향으로 s만큼 이동 후 N과 나머지 연산을 해줌
            new_fireballs[(ny, nx)].append((m, s, d))
 
    fireballs = new_fireballs.copy()
 
def all_odd_or_even(dirs):
    # 방향이 모두 홀수 혹은 짝수인 경우 True, 아닌 경우 False 반환
    odd_flag, even_flag = FalseFalse
    for d in dirs:
        if d % 2 == 1:
            odd_flag = True # 홀수 발견
        if d % 2 == 0:
            even_flag = True # 짝수 발견
 
    if odd_flag and even_flag: # 홀/짝 모두 발견된 경우 False 반환
        return False
    return True
 
 
def change_duplicate_fireballs():
    # 좌표가 중복된 파이어볼 처리
    global fireballs
    new_fireballs = defaultdict(list) # 처리후의 파이어볼 정보
 
    for loc, info_ilst in fireballs.items():
        if len(info_ilst) == 1# 해당 좌표에 파이어볼이 1개인 경우
            new_fireballs[loc].append(info_ilst[0]) 
            continue
 
        # 파이어볼이 중복된 경우
        sum_m, sum_s, dirs = 00, [] # 질량합, 속도합, 방향리스트
        for m, s, d in info_ilst:
            sum_m += m
            sum_s += s
            dirs.append(d)
        new_m = int(sum_m / 5# 새로운 파이어볼 질량
        if new_m == 0# 질량이 0인 경우 소멸되므로 continue
            continue
        new_s = int(sum_s/len(info_ilst)) # 새로운 파이어볼 속도
        new_dirs = [0246if all_odd_or_even(dirs) else [1357# 새로운 파이어볼 방향(all_odd_or_even() 함수의 결과에 따름)
        for new_d in new_dirs:
            new_fireballs[loc].append((new_m, new_s, new_d)) 
 
    fireballs = new_fireballs.copy()
 
 
for _ in range(K): # k번 반복
    move_fireballs()
    change_duplicate_fireballs()
 
result = 0
for loc, info_list in fireballs.items(): # 파이어볼 리스트에서 질량합을 구함
    for m, s, d in info_list:
        result += m
print(result)
 
cs

 

제출 결과