본문 바로가기
백준 문제풀이

[백준] 32930번 슈팅 연습 - Python(1편)

by arctis7p 2024. 12. 20.

문제 링크: https://www.acmicpc.net/problem/32930

아래는 문제 사진입니다.

더보기
백준 32930번 슈팅 연습 스크린샷

 

 

 이 문제는 처음에 N개의 과녁이 주어지고, 그 중 원점에서 가장 멀리 있는 과녁을 선택하여 커서가 해당 과녁으로 이동합니다. 이동한 후에는 그 과녁을 삭제하고, 남은 M개의 과녁에서 인덱스가 앞인 순서대로 새로운 과녁을 추가하여 다시 N개의 과녁을 구성합니다. 이후 현재 커서에서 가장 멀리 있는 과녁을 찾아 이동하는 과정을 M번 반복하며, 이때 얻는 점수는 이동한 거리의 제곱입니다. 이를 바탕으로 문제 해결 과정을 다음과 같이 정리할 수 있습니다.

  1. 입력을 받습니다.
  2. 원점에서 가장 멀리 떨어져 있는 과녁을 찾아 이동합니다.
  3. 점수를 기록하고 해당 좌표를 삭제합니다.
  4. M개의 과녁에서 가장 앞에 있는 요소를 과녁으로 추가합니다.
  5. 이 과정을 M번 반복하여 점수를 출력합니다.

 전체 코드를 확인해보겠습니다.

# 입력 받기
N, M = map(int, input().split())
visible_targets = [tuple(map(int, input().split())) for _ in range(N)]
hidden_targets = [tuple(map(int, input().split())) for _ in range(M)]

# 현재 위치를 원점으로 초기화
cursor_position = (0, 0)
total_score = 0

# 가장 멀리 떨어진 과녁 찾기
def find_farthest_visible_target(cursor_position, targets):
    return max(targets, key=lambda p: (p[0] - cursor_position[0])**2 + (p[1] - cursor_position[1])**2)

# 점수 계산
def calculate_score(cursor, target):
    distance_squared = (target[0] - cursor[0])**2 + (target[1] - cursor[1])**2
    return round(distance_squared)

# 과녁 업데이트
def update_targets(show_data, hidden_targets, farthest_target):
    show_data.remove(farthest_target)
    if hidden_targets:
        show_data.append(hidden_targets.pop(0))
    return show_data, hidden_targets

# M번 반복하여 총 점수 계산
for _ in range(M):
    farthest_target = find_farthest_visible_target(cursor_position, visible_targets)
    score = calculate_score(cursor_position, farthest_target)
    total_score += score
    cursor_position = farthest_target
    visible_targets, hidden_targets = update_targets(visible_targets, hidden_targets, farthest_target)

# 총 점수 출력
print(total_score)

 

부분 부분 나눠서 설명드리겠습니다.

N, M = map(int, input().split())
visible_targets = [tuple(map(int, input().split())) for _ in range(N)]
hidden_targets = [tuple(map(int, input().split())) for _ in range(M)]

N(초기 화면에 보이는 과녁 수) M(총 이동 횟수) map 함수와 언패킹을 이용하여 입력받습니다. visible_targets에 초기에 보이는 과녁의 좌표를, hidden_targets에 나중에 나타날 과녁의 좌표를 저장합니다. 이때, 간결함을 위해 리스트 컴프리헨션을 사용했으며, 각 좌표는 튜플로 저장합니다. visible_targets는 현재 화면에 보이는 과녁들의 좌표를, hidden_targets는 앞으로 나타날 과녁들의 좌표를 나타냅니다.

 

# 현재 위치를 원점으로 초기화
cursor_position = (0, 0)
total_score = 0

 시작 위치가 원점이므로 초기 cursor_position (0, 0)으로 두었습니다. 또한, 마지막에 출력하기 위한 점수를 기록할 total_score 0으로 초기화했습니다.

 

다음은 주요 함수들에 대해 알아보도록 하겠습니다.

# 가장 멀리 떨어진 과녁 찾기
def find_farthest_visible_target(cursor_position, targets):
    return max(targets, key=lambda p: (p[0] - cursor_position[0])**2 + (p[1] - cursor_position[1])**2)

 이 함수는 현재 커서 위치에서 가장 멀리 있는 과녁을 찾아 해당 좌표를 튜플 형태로 반환합니다. 여기서 lambda p를 사용한 이유는 p 2차원 좌표를 나타내는 점(point)을 의미하기 때문입니다. p라는 변수명은 직관적으로 좌표를 나타내며, 가독성을 높이는 데 기여합니다. 반면, x를 사용하면 x좌표만을 다루는 것으로 오해할 여지가 있으므로 적합하지 않을 수 있습니다. 하지만 변수명이 코드의 동작에 영향을 미치지 않으므로, x를 사용해도 기능적으로는 동일합니다이렇게 수정하면 함수의 역할과 변수명 선택의 이유가 더 명확하게 전달됩니다.

 

# 점수 계산
def calculate_score(cursor, target):
    distance_squared = (target[0] - cursor[0])**2 + (target[1] - cursor[1])**2
    return round(distance_squared)

 이 함수는 커서와 과녁 사이의 거리의 제곱을 계산하여 점수를 반환합니다. 거리의 제곱을 계산하기 때문에 결과는 항상 정수가 됩니다. 따라서 round() 함수는 불필요하며, 단순히 return distance_squared로 작성하는 것이 더 효율적입니다. 일반적으로 거리 계산에서는 부동소수점 연산으로 인한 미세한 오차가 발생할 수 있습니다. 그러한 경우 round() 함수를 사용하여 오차를 제거할 수 있습니다. 하지만 이 문제에서는 정수 좌표만을 다루고 거리의 제곱을 사용하므로 오차 발생 가능성이 없습니다만, 거리를 다루기에 round() 함수를 사용했습니다.

 

# 과녁 업데이트
def update_targets(show_data, hidden_targets, farthest_target):
    show_data.remove(farthest_target)
    if hidden_targets:
        show_data.append(hidden_targets.pop(0))
    return show_data, hidden_targets

 이 함수는 맞힌 과녁을 제거하고, 숨겨진 과녁 중 하나를 추가하여 과녁 목록을 업데이트합니다. ‘if hidden_targets:’ 조건문은 ‘hidden_targets’ 리스트가 비어있지 않은지 검사합니다. Python에서 비어있지 않은 리스트는 'truthy', 빈 리스트는 'falsy'로 평가됩니다. 따라서 이 조건은 "hidden_targets 리스트에 요소가 하나라도 있다면…"이라는 의미가 됩니다.

 

 이 검사를 하는 이유는 hidden_targets.pop(0)을 안전하게 수행하기 위함입니다. 리스트가 비어있을 때 pop() 함수를 실행하면 IndexError가 발생하기 때문에, 이를 방지하고자 사전에 리스트의 비어있음을 확인합니다.

 

# M번 반복하여 총 점수 계산
for _ in range(M):
    farthest_target = find_farthest_visible_target(cursor_position, visible_targets)
    score = calculate_score(cursor_position, farthest_target)
    total_score += score
    cursor_position = farthest_target
    visible_targets, hidden_targets = update_targets(visible_targets, hidden_targets, farthest_target)

 마지막 부분은 문제의 핵심 로직을 구현한 메인 루프입니다. M번 반복하며 다음 과정을 수행합니다:

  1. 현재 커서 위치에서 가장 멀리 있는 과녁을 찾습니다.
  2. 해당 과녁까지의 거리에 따른 점수를 계산합니다.
  3. 계산된 점수를 총 점수에 더합니다.
  4. 커서 위치를 맞힌 과녁의 위치로 업데이트합니다.
  5. 과녁 목록을 갱신합니다 (맞힌 과녁 제거 및 새 과녁 추가).

이 과정을 M번 반복함으로써, 모든 이동에 대한 점수를 누적하고 과녁 상태를 지속적으로 업데이트합니다. 루프가 종료되면 total_score에 최종 점수가 저장되어 제출됩니다.