시뮬레이션, DFS - 왕실의 기사 대결

jisu_log·2025년 9월 24일

알고리즘 문제풀이

목록 보기
104/105

  • 재귀 구현 시, for문 안에서 재귀함수 호출할 때 값 여러개 리턴되는데 하나의 변수에 다 할당하는 실수 주의하기
  • move_knight()에서 실수하지 않기
  • 이미 다른 기사가 한번 밀었던 기사라면 패스(하나당 한 번만 밀어야 함)!! 면적이 w x h이므로 맞닿은 기사가 여러명일 수 있음 주의하기
from collections import defaultdict


L, N, Q = map(int, input().split())

maps = []

# 기사들의 위치를 표시할 맵
k_maps = [[0] * L for _ in range(L)]


for i in range(L):
    line = list(map(int, input().split()))

    maps.append(line)



positions = defaultdict(tuple)
bodys = defaultdict(tuple)
origin_hearts = defaultdict(int)
hearts = defaultdict(int)

# 죽은 기사 리스트
out_list = []


# 각 기사의 위치를 맵에 그리는 함수
def draw_body(r, c, num):

    h, w = bodys[num]

    for i in range(h):
        for j in range(w):
            
            if r + i >= L or c + j >= L:
                print(num, '이 맵 밖을 벗어남', r + i, c + j)
                return 0

            k_maps[r + i][c + j] = num


def count_damage(num):
    h, w = bodys[num]
    r, c = positions[num]

    cnt = 0

    for i in range(h):
        for j in range(w):
            # 해당 영역에 1(함정)이 있으면 누적
            if r + i >= L or c + j >= L:
                print(num, '이 맵 밖을 벗어남', r + i, c + j)
                return 0
            if maps[r + i][c + j] == 1:
                cnt += 1
    
    return cnt


for i in range(1, N + 1): # 기사 번호: 1 ~ N 번
    r, c, h, w, k = map(int, input().split())
    # 0부터 시작하도록 인덱스 맞춰주기!
    r -= 1
    c -= 1

    positions[i] = (r, c)
    bodys[i] = (h, w)
    hearts[i] = k
    origin_hearts[i] = k

    draw_body(r, c, i)



dx = [-1, 0, 1, 0]
dy = [0, 1, 0, -1]



# 기사가 이동하는 방향에 따라 고려해야 하는 면의 좌표들을 반환
def get_part(r, c, num, d):
    part_list = []
    h, w = bodys[num]

    if d == 0:
        for i in range(w):
            part_list.append((r, c + i))
    elif d == 1:
        for i in range(h):
            part_list.append((r + i, c + w - 1))
    elif d == 2:
        for i in range(w):
            part_list.append((r + h - 1, c + i))
    elif d == 3:
        for i in range(h):
            part_list.append((r + i, c))
    
    return part_list
        

# --------------------------------------------------------------
def move_knight(n, direction):

    # 밀거나 밀려서 이동하는 기사 리스트
    move_knight_list = set()
    move_knight_list.add(n)


    def move_check(num, d):
        r, c = positions[num]

        parts = get_part(r, c, num, d)


        for r, c in parts:

            nr, nc = r + dx[d], c + dy[d]

            if nr < 0 or nr >= L or nc < 0 or nc >= L:
                return False

            
            # 이동할 곳에 하나라도 벽이 있다면
            if maps[nr][nc] == 2:
                return False


            # 이동할 곳에 다른 기사가 있다면 같은 방향으로 밀기
            if k_maps[nr][nc] > 0:

                another_num = k_maps[nr][nc]

                # 이미 다른 기사가 한번 밀었던 기사라면 패스(하나당 한 번만 밀어야 함)!! 맞닿은 기사가 여러명일 수 있음 주의!
                if another_num in move_knight_list:
                    continue
                # 같이 밀리는 기사 리스트 추가
                move_knight_list.add(another_num)

                # 재귀 호출
                # for문 안에 있으므로 여러개의 move_check 값이 리턴됨 주의!! 하나라도 False가 나오면 바로 return False 하기
                if not move_check(another_num, d):
                    return False

        return True
            

    is_possible = move_check(n, direction)


    # 같이 밀리는 기사 리스트와 움직임 가능 여부 리턴
    return list(move_knight_list), is_possible

# --------------------------------------------------------------------


# 명령
for i in range(Q):
    # move_num이 move_dir 방향으로 1칸 이동하라
    move_num, move_dir = map(int, input().split())


    # 사라진 기사에게 명령하면 패스
    if move_num in out_list:
        continue
    

    # 1) 기사 이동
    move_knight_list, is_possible = move_knight(move_num, move_dir)


    # 해당 방향으로 이동이 가능하다면, 각 기사의 좌표 이동시키기
    if is_possible:
        for num in move_knight_list:
            origin_r, origin_c = positions[num]

            nr, nc = origin_r + dx[move_dir], origin_c + dy[move_dir]

            positions[num] = (nr, nc)
    # 이동 불가능하다면 아무일 일어나지 않음
    else:
        continue
    
    # 이동 후, 밀려난 기사들 피해 누적하기
    for num in move_knight_list:
        # 명령받은 기사는 피해 제외
        if num == move_num:
            continue

        damage = count_damage(num)

        
        # 현재 체력 이상의 대미지 입을 시 사라짐
        if damage >= hearts[num]:
            out_list.append(num)
            del hearts[num]
            del positions[num]
            del origin_hearts[num]

        # 대미지보다 체력이 더 많은 경우
        else:
            hearts[num] -= damage
    
    # 이동한 기사들 포함 생존한 모든 기사들을 k_maps 초기화 후 위치 다시 그려주기
    k_maps = [[0] * L for _ in range(L)]

    for key, value in positions.items():

        draw_body(value[0], value[1], key)




# 살아남은 기사들의 시작 체력을 모두 더한 후, 최종 체력을 모두 더해서 빼주면 생존한 기사들의 총 데미지의 합이 됨
print(sum(origin_hearts.values()) - sum(hearts.values()))

0개의 댓글