728x90
반응형

문제 설명

물류 센터에는 n개의 포인트가 존재하며, 각 포인트는 (r, c) 형태의 2차원 좌표를 가집니다.

여러 대의 로봇은 각각 정해진 운송 경로를 따라 이동합니다.

각 로봇의 경로에는 방문해야 하는 포인트 번호가 순서대로 주어지며, 로봇은 첫 번째 포인트에서 시작하여 나머지 포인트를 차례대로 방문합니다.

모든 로봇은 0초에 동시에 출발합니다.

로봇은 1초마다 다음과 같이 한 칸 이동할 수 있습니다.

  • r 좌표를 1 증가 또는 감소
  • c 좌표를 1 증가 또는 감소

로봇은 항상 다음 포인트까지 최단 경로로 이동합니다.

최단 경로가 여러 개 존재하는 경우에는

r 좌표를 먼저 이동한 뒤 c 좌표를 이동합니다.

마지막 포인트에 도착한 로봇은 운송을 종료하고 물류 센터를 벗어납니다.

같은 시간에 같은 좌표에 2대 이상의 로봇이 존재한다면 위험 상황이 1번 발생한 것으로 판단합니다.

같은 시간에 여러 좌표에서 위험 상황이 발생한다면 각각을 모두 계산해야 합니다.

모든 로봇의 운송이 끝날 때까지 발생하는 위험 상황의 총횟수를 구해야 합니다.


제한사항

  • 2 ≤ n ≤ 100
  • 각 포인트의 좌표는 (r, c) 형태로 주어집니다.
  • 1 ≤ r, c ≤ 100
  • 서로 다른 포인트가 같은 좌표를 가지는 경우는 없습니다.
  • 로봇의 수는 최대 100대입니다.
  • 각 로봇은 최대 100개의 포인트를 방문합니다.
  • 모든 로봇의 운송 경로 길이는 같습니다.
  • 같은 포인트를 연속으로 방문하는 경우는 없습니다.

입출력 예

pointsroutesresult

[[3, 2], [6, 4], [4, 7], [1, 4]] [[4, 2], [1, 3], [2, 4]] 1
[[3, 2], [6, 4], [4, 7], [1, 4]] [[4, 2], [1, 3], [4, 2], [4, 3]] 9
[[2, 2], [2, 3], [2, 7], [6, 6], [5, 2]] [[2, 3, 4, 5], [1, 3, 4, 5]] 0

문제 풀이

이 문제는 모든 로봇이 움직이는 과정을 직접 확인하는 시뮬레이션 문제입니다.

핵심은 각 로봇이

몇 초에 어떤 좌표에 있는지

를 구한 뒤, 같은 시간과 같은 좌표에 존재하는 로봇의 수를 세는 것입니다.

로봇의 이동 방법이 완전히 정해져 있기 때문에 각 로봇의 전체 이동 경로를 미리 구할 수 있습니다.


1. 포인트 번호를 실제 좌표로 변환하기

routes에는 좌표가 직접 들어 있는 것이 아니라 방문해야 하는 포인트 번호가 들어 있습니다.

예를 들어

points[0] = [3, 2]

라면 1번 포인트의 좌표는

(3, 2)

입니다.

따라서 로봇의 경로가

[4, 2]

라면 실제로는

4번 포인트의 좌표 → 2번 포인트의 좌표

순서대로 이동하게 됩니다.

각 로봇마다 방문할 포인트 번호를 실제 (r, c) 좌표로 변환하면서 이동 경로를 만들면 됩니다.


2. 로봇은 항상 r 좌표를 먼저 이동한다

현재 위치가

(r1, c1)

이고 다음 목적지가

(r2, c2)

라고 하겠습니다.

두 좌표 사이의 최단 거리는

|r1 - r2| + |c1 - c2|

입니다.

하지만 최단 경로는 여러 개 존재할 수 있습니다.

문제에서는 이동 방법까지 정해주었습니다.

최단 경로가 여러 개라면 r 좌표가 변하는 이동을 먼저 한다.

따라서 로봇은 먼저 r1을 r2와 같게 만든 뒤, c1을 c2와 같게 만들면 됩니다.

예를 들어

(1, 2) → (3, 4)

로 이동한다고 하겠습니다.

먼저 r 좌표를 이동하면

(1, 2) → (2, 2) → (3, 2)

가 됩니다.

그다음 c 좌표를 이동하면

(3, 2) → (3, 3) → (3, 4)

가 됩니다.

따라서 전체 이동 경로는

(1, 2) → (2, 2) → (3, 2) → (3, 3) → (3, 4)

로 하나로 결정됩니다.


3. 각 시간의 로봇 위치 기록하기

모든 로봇은 0초에 첫 번째 포인트에서 시작합니다.

따라서 출발 위치 역시 위험 상황을 판단할 때 포함해야 합니다.

예를 들어 어떤 로봇의 이동 경로가

(1, 2) → (2, 2) → (3, 2) → (3, 3)

이라면 시간에 따른 위치는 다음과 같습니다.

시간위치

0초 (1, 2)
1초 (2, 2)
2초 (3, 2)
3초 (3, 3)

따라서 각 로봇을 이동시키면서

시간 → 위치

정보를 기록할 수 있습니다.

모든 로봇에 대해 이 과정을 반복하면 특정 시간에 어떤 로봇들이 어느 좌표에 있었는지 확인할 수 있습니다.


4. 중간 포인트에 도착해도 다음 이동은 바로 이어진다

하나의 로봇은 여러 개의 포인트를 순서대로 방문합니다.

예를 들어 경로가

1번 → 3번 → 4번

이라고 하겠습니다.

로봇이 3번 포인트에 도착했다고 해서 한 번 더 정지하는 것은 아닙니다.

3번 포인트에 도착한 시간이 5초라면

  • 5초에는 3번 포인트에 존재하고
  • 6초에는 4번 포인트를 향해 한 칸 이동합니다.

따라서 각 구간을 연결할 때 중간 포인트의 위치를 중복해서 기록하지 않는 것이 중요합니다.


5. 같은 시간과 같은 좌표의 로봇 수를 센다

이제 모든 로봇의 위치를 시간별로 확인합니다.

특정 시간에 특정 좌표에 몇 대의 로봇이 존재하는지를 저장합니다.

예를 들어 5초의 로봇 위치가 다음과 같다고 하겠습니다.

로봇위치

1번 (3, 4)
2번 (3, 4)
3번 (2, 7)
4번 (5, 2)

(3, 4)에 로봇이 2대 존재하므로 위험 상황이 발생합니다.

따라서 이 시간에는 위험 상황을 1 증가시킵니다.


6. 로봇이 3대 이상 모여도 위험 상황은 1번이다

이 문제에서 특히 주의해야 할 부분입니다.

같은 시간, 같은 좌표에 로봇이 3대 있다고 하더라도 위험 상황을 로봇의 쌍 개수만큼 계산하는 것이 아닙니다.

예를 들어 한 좌표에 로봇이 3대 있다면

3C2 = 3

개의 충돌 가능성이 있다고 계산하는 것이 아니라,

해당 좌표에서 위험 상황이 발생했다는 사실 자체를 1번 계산합니다.

즉,

같은 좌표의 로봇 수위험 상황 횟수

1대 0
2대 1
3대 1
4대 1

입니다.

따라서 각 시간과 좌표에 대해 로봇의 수가 2 이상인지만 확인하면 됩니다.


7. 같은 시간에 여러 좌표에서 충돌하면 각각 계산한다

반대로 같은 시간에 서로 다른 여러 좌표에서 위험 상황이 발생할 수 있습니다.

예를 들어 10초에

  • (2, 3)에 로봇 2대
  • (5, 7)에 로봇 3대

가 있다고 하겠습니다.

두 좌표 모두 위험 상황이므로 해당 시간의 위험 상황은 총

2번

입니다.

즉, 위험 상황은

시간 + 좌표

를 하나의 기준으로 생각해야 합니다.


8. 마지막 포인트에 도착한 순간까지는 포함한다

로봇은 마지막 포인트에 도착하면 물류 센터를 벗어납니다.

따라서 마지막 포인트에 도착한 그 순간의 위치는 위험 상황 판단에 포함됩니다.

예를 들어 로봇이 7초에 마지막 포인트에 도착했다면

7초에는 해당 좌표에 존재합니다.

따라서 다른 로봇이 같은 7초에 같은 좌표에 있다면 위험 상황이 발생합니다.

하지만 8초부터는 물류 센터를 벗어났으므로 더 이상 위치를 확인하지 않습니다.


전체 흐름

문제를 해결하는 과정은 다음과 같습니다.

  1. 각 로봇의 첫 번째 포인트 좌표를 0초 위치로 기록합니다.
  2. 현재 포인트에서 다음 포인트로 이동합니다.
  3. r 좌표가 다르다면 r 좌표부터 목적지와 같아질 때까지 이동합니다.
  4. 이후 c 좌표가 같아질 때까지 이동합니다.
  5. 한 칸 이동할 때마다 시간을 1 증가시키고 현재 위치를 기록합니다.
  6. 모든 로봇의 전체 이동 경로에 대해 같은 시간과 같은 좌표에 몇 대의 로봇이 존재하는지 계산합니다.
  7. 로봇이 2대 이상 존재하는 (시간, 좌표)마다 위험 상황을 1씩 증가시킵니다.
  8. 모든 위험 상황의 수를 더해 반환합니다.

핵심 정리

이 문제는 로봇의 이동 경로가 정해져 있기 때문에 각 로봇의 위치를 시간 순서대로 시뮬레이션하는 것이 핵심입니다.

특히 다음 조건들을 정확하게 처리해야 합니다.

  • 모든 로봇은 0초의 시작 위치부터 위험 상황 판단에 포함됩니다.
  • 최단 경로 중 반드시 r 좌표를 먼저 이동합니다.
  • 중간 포인트에서는 별도로 대기하지 않습니다.
  • 같은 시간, 같은 좌표에 로봇이 2대 이상이면 위험 상황은 1번입니다.
  • 로봇이 3대 이상 있어도 위험 상황을 여러 번 세지 않습니다.
  • 같은 시간이라도 서로 다른 좌표에서 발생한 위험 상황은 각각 계산합니다.
  • 마지막 포인트에 도착한 순간까지는 위험 상황 판단에 포함됩니다.

결국 모든 로봇에 대해

시간 + 좌표

를 기록한 뒤, 동일한 값이 2번 이상 등장하는 경우를 세면 문제를 해결할 수 있습니다.


시간 복잡도

각 포인트의 좌표는 1 ~ 100 범위이므로 두 포인트 사이의 최대 맨해튼 거리는

99 + 99 = 198

입니다.

각 로봇이 방문하는 포인트의 개수를 M, 로봇의 수를 X라고 하면 한 로봇이 이동하는 최대 거리는 대략

O(M × 198)

입니다.

모든 로봇의 이동 경로를 확인하므로 전체 시간 복잡도는

O(X × M × D)

로 볼 수 있습니다.

여기서 D는 두 포인트 사이의 최대 이동 거리이며 최대 198입니다.

문제에서

  • 로봇의 수 X ≤ 100
  • 경로 길이 M ≤ 100
  • 한 구간의 이동 거리 D ≤ 198

이므로 모든 로봇의 이동을 직접 시뮬레이션해도 충분히 해결할 수 있습니다.

from collections import Counter

def solution(points, routes):
    answer = 0
    
    robot_path = []
    for route in routes:
        nr, nc = points[route[0]-1]
        path = [(nr, nc)]
        
        for point_num in route[1:]:
            r, c = points[point_num-1]
            while nr != r:
                if nr < r:
                    nr += 1
                else:
                    nr -= 1
                path.append((nr, nc))
            while nc != c:
                if nc < c:
                    nc += 1
                else:
                    nc -= 1
                path.append((nr, nc))
        robot_path.append(path)  
    
    max_time = max(len(path) for path in robot_path)
    
    for time in range(max_time):
        position = []
        
        for path in robot_path:
            if time < len(path):
                position.append(path[time])
        
        counter = Counter(position)
        
        for point, count in counter.items():
            if count >= 2:
                answer += 1
    
    return answer
728x90
반응형
728x90
반응형

문제 설명

순서대로 n개의 퍼즐을 제한 시간 안에 모두 해결해야 합니다.

각 퍼즐에는 다음 정보가 존재합니다.

  • diff : 퍼즐의 난이도
  • time_cur : 현재 퍼즐을 푸는 데 필요한 시간
  • time_prev : 이전 퍼즐을 푸는 데 필요한 시간
  • level : 현재 숙련도

퍼즐을 푸는 방식은 숙련도에 따라 달라집니다.

diff ≤ level인 경우

현재 숙련도가 퍼즐의 난이도 이상이라면 퍼즐을 틀리지 않고 바로 해결할 수 있습니다.

따라서 필요한 시간은

time_cur

입니다.

diff > level인 경우

숙련도가 퍼즐의 난이도보다 낮다면

diff - level

번 퍼즐을 틀리게 됩니다.

한 번 틀릴 때마다

  • 현재 퍼즐을 푸는 시간 time_cur
  • 이전 퍼즐을 다시 푸는 시간 time_prev

가 필요합니다.

따라서 한 번 틀릴 때마다

time_cur + time_prev

만큼 시간이 필요합니다.

그리고 모든 실패가 끝난 뒤 현재 퍼즐을 한 번 더 풀어야 하므로 최종적으로 필요한 시간은

(diff - level) × (time_cur + time_prev) + time_cur

입니다.

모든 퍼즐을 해결하는 데 걸리는 시간이 limit 이하가 되도록 하는 **최소 숙련도 level**을 구해야 합니다.


제한사항

  • 1 ≤ n ≤ 300,000
  • diffs[i]는 i번째 퍼즐의 난이도입니다.
  • times[i]는 i번째 퍼즐의 소요 시간입니다.
  • diffs[0] = 1
  • 1 ≤ diffs[i] ≤ 100,000
  • 1 ≤ times[i] ≤ 10,000
  • 1 ≤ limit ≤ 10^15
  • 제한 시간 내에 모든 퍼즐을 해결할 수 있는 경우만 입력으로 주어집니다.

입출력 예

diffstimeslimitresult

[1, 5, 3] [2, 4, 7] 30 3
[1, 4, 4, 2] [6, 3, 8, 2] 59 2
[1, 328, 467, 209, 54] [2, 7, 1, 4, 3] 1723 294
[1, 99999, 100000, 99995] [9999, 9001, 9999, 9001] 3456789012 39354

문제 풀이

이 문제에서 중요한 점은 숙련도가 높아질수록 모든 퍼즐을 푸는 데 필요한 시간이 줄어든다는 것입니다.

따라서 특정 숙련도에서 제한 시간 안에 퍼즐을 모두 해결할 수 있는지를 확인할 수 있다면,
최소 숙련도를 이분 탐색으로 찾을 수 있습니다.


1. 특정 숙련도에서 걸리는 시간 계산하기

먼저 숙련도 level이 정해져 있다고 생각해보겠습니다.

각 퍼즐의 난이도 diff와 level을 비교하면 해당 퍼즐에 필요한 시간을 계산할 수 있습니다.

숙련도가 충분한 경우

diff ≤ level이라면 퍼즐을 틀리지 않으므로

time_cur

만큼의 시간이 필요합니다.


숙련도가 부족한 경우

diff > level이라면

diff - level

번 틀리게 됩니다.

한 번 틀릴 때마다 현재 퍼즐과 이전 퍼즐을 다시 풀어야 하므로

time_cur + time_prev

만큼 시간이 필요합니다.

따라서 실패 과정에서 필요한 시간은

(diff - level) × (time_cur + time_prev)

이고,

마지막으로 현재 퍼즐을 성공하는 데 time_cur이 한 번 더 필요합니다.

따라서 현재 퍼즐에 필요한 전체 시간은

(diff - level) × (time_cur + time_prev) + time_cur

입니다.


2. 첫 번째 퍼즐 처리

첫 번째 퍼즐의 난이도는 항상 1이고 숙련도 역시 1 이상이므로 첫 번째 퍼즐은 항상 한 번에 풀 수 있습니다.

따라서 첫 번째 퍼즐에서는

times[0]

만큼의 시간만 사용합니다.

이후 퍼즐부터 이전 퍼즐의 시간이 필요하기 때문에

time_prev = times[i - 1]

을 사용하면 됩니다.


3. 숙련도가 높을수록 필요한 시간은 감소한다

이 문제에서 이분 탐색을 사용할 수 있는 이유는 숙련도와 소요 시간의 관계가 일정하기 때문입니다.

예를 들어 난이도가 5인 퍼즐이 있다고 해보겠습니다.

숙련도가 증가하면 실패 횟수는 다음과 같이 변합니다.

level실패 횟수

1 4
2 3
3 2
4 1
5 이상 0

즉, 숙련도가 증가할수록 실패 횟수는 줄어듭니다.

따라서 전체 소요 시간 역시 항상 같거나 감소합니다.

즉,

  • 어떤 숙련도 level에서 제한 시간 안에 해결할 수 있다면
  • 그보다 높은 모든 숙련도에서도 해결할 수 있습니다.

반대로,

  • 어떤 숙련도에서 제한 시간을 초과했다면
  • 그보다 낮은 숙련도에서도 해결할 수 없습니다.

이를 정리하면 다음과 같은 형태가 됩니다.

실패 실패 실패 ... 성공 성공 성공

우리가 찾으려는 것은 여기서 처음 성공하는 숙련도입니다.

이런 형태의 문제는 이분 탐색으로 해결할 수 있습니다.


4. 이분 탐색 범위 설정

숙련도는 양의 정수이므로 최소값은

1

입니다.

숙련도가 모든 퍼즐의 최대 난이도 이상이라면 어떤 퍼즐에서도 틀리지 않습니다.

따라서 필요한 숙련도의 최댓값은

max(diffs)

까지만 확인하면 충분합니다.

이분 탐색의 범위는

1 ~ max(diffs)

가 됩니다.


5. 중간 숙련도로 제한 시간을 만족하는지 확인

현재 탐색 범위의 중간값을

mid

라고 하겠습니다.

mid를 현재 숙련도라고 가정하고 모든 퍼즐을 순회하면서 전체 소요 시간을 계산합니다.

전체 시간이 limit 이하라면 현재 숙련도로 모든 퍼즐을 해결할 수 있다는 의미입니다.

하지만 문제에서는 가능한 숙련도가 아니라 최소 숙련도를 요구하고 있습니다.

따라서 더 작은 숙련도에서도 해결할 수 있는지 확인하기 위해 왼쪽 범위를 탐색합니다.

반대로 전체 시간이 limit보다 크다면 현재 숙련도가 부족하다는 뜻이므로 숙련도를 높여야 합니다.

따라서 오른쪽 범위를 탐색합니다.

이를 반복하면 제한 시간을 만족하는 최소 숙련도를 찾을 수 있습니다.


6. 제한 시간을 초과하면 즉시 계산 종료

퍼즐의 개수는 최대 300,000개입니다.

특정 숙련도에서 전체 시간을 계산하는 도중 이미

total_time > limit

이 되었다면 남은 퍼즐을 확인할 필요가 없습니다.

이후에는 시간이 감소하지 않기 때문에 현재 숙련도로는 제한 시간을 만족할 수 없다는 사실이 이미 결정되었기 때문입니다.

따라서 이 순간 바로 계산을 종료하면 불필요한 연산을 줄일 수 있습니다.


예제 1

diffs = [1, 5, 3]

times = [2, 4, 7]

limit = 30

이라고 해보겠습니다.

숙련도를 3이라고 하면 다음과 같습니다.

첫 번째 퍼즐

난이도는 1이고 숙련도는 3이므로 바로 해결할 수 있습니다.

소요 시간은

2

입니다.


두 번째 퍼즐

난이도는 5, 숙련도는 3입니다.

따라서

5 - 3 = 2

번 틀립니다.

현재 퍼즐의 소요 시간은 4, 이전 퍼즐의 소요 시간은 2이므로 한 번 틀릴 때 필요한 시간은

4 + 2 = 6

입니다.

따라서 전체 시간은

6 × 2 + 4 = 16

입니다.


세 번째 퍼즐

난이도가 3이고 숙련도 역시 3이므로 틀리지 않고 해결합니다.

필요한 시간은

7

입니다.


전체 시간은

2 + 16 + 7 = 25

입니다.

제한 시간인 30 이하이므로 숙련도 3이면 모든 퍼즐을 해결할 수 있습니다.

반면 숙련도 2에서는 제한 시간을 초과하므로 최소 숙련도는

3

이 됩니다.


핵심 정리

이 문제는 각 숙련도를 1부터 하나씩 증가시키며 확인하면 너무 많은 연산이 필요합니다.

대신 다음 두 가지 성질을 이용합니다.

  1. 특정 숙련도가 주어지면 모든 퍼즐의 전체 소요 시간을 계산할 수 있습니다.
  2. 숙련도가 증가할수록 전체 소요 시간은 절대 증가하지 않습니다.

따라서

제한 시간을 만족하는 최소 숙련도

를 찾는 문제로 바꿔 생각할 수 있고, 이를 이분 탐색으로 해결할 수 있습니다.


시간 복잡도

퍼즐의 개수를 N, 최대 난이도를 D라고 하겠습니다.

특정 숙련도가 가능한지 확인하려면 모든 퍼즐을 한 번 확인해야 하므로

O(N)

의 시간이 필요합니다.

숙련도 범위 1 ~ D에서 이분 탐색을 진행하므로

O(log D)

번 확인합니다.

따라서 전체 시간 복잡도는

O(N log D)

입니다.

N은 최대 300,000, D는 최대 100,000이므로 충분히 빠르게 해결할 수 있습니다.

def solution(diffs, times, limit):
    left, right = 1, max(diffs)
    answer = right
    
    while left <= right:
        mid = (left + right) // 2
        
        # mid 숙련도로 퍼즐들을 푸는 데 걸리는 총 시간 계산
        total_time = 0
        prev_time = 0
        for diff, time in zip(diffs, times):
            if diff > mid:
                total_time += (diff - mid) * (prev_time + time) + time
            else:
                total_time += time
            prev_time = time
            
        # 시간 내에 해결 가능한 경우: 더 낮은 숙련도 탐색
        if total_time <= limit:
            answer = mid
            right = mid - 1
        # 제한 시간을 초과한 경우: 더 높은 숙련도 필요
        else:
            left = mid + 1
            
    return answer
728x90
반응형
728x90
반응형

문제 설명

게임 캐릭터는 붕대 감기 기술을 사용해 체력을 회복할 수 있습니다.

붕대 감기는 t초 동안 지속되며, 붕대를 감는 동안 매초 x만큼의 체력을 회복합니다.
또한 t초 동안 한 번도 공격받지 않고 연속으로 붕대를 감는 데 성공하면 y만큼의 체력을 추가로 회복합니다.

단, 체력은 최대 체력을 초과할 수 없습니다.

몬스터의 공격을 받는 순간에는 체력을 회복할 수 없으며, 진행 중이던 붕대 감기도 취소됩니다.
공격 이후에는 다시 붕대 감기를 시작하고, 연속 성공 시간은 0부터 다시 계산합니다.

몬스터에게 공격받아 현재 체력이 0 이하가 되면 캐릭터가 죽게 됩니다.

붕대 감기의 정보와 캐릭터의 최대 체력, 몬스터의 공격 시간이 주어졌을 때 모든 공격이 끝난 직후 남아 있는 체력을 구해야 합니다.

캐릭터가 중간에 죽는 경우 -1을 반환합니다.

제한 사항

  • bandage = [t, x, y]
    • t : 붕대 감기 시전 시간
    • x : 1초당 회복량
    • y : 연속 시전 성공 시 추가 회복량
  • 1 ≤ t ≤ 50
  • 1 ≤ x ≤ 100
  • 1 ≤ y ≤ 100
  • 1 ≤ health ≤ 1,000
  • 1 ≤ attacks의 길이 ≤ 100
  • attacks[i] = [공격 시간, 피해량]
  • 공격 시간은 오름차순으로 주어지며 서로 중복되지 않습니다.

입출력 예

bandagehealthattacksresult

[5, 1, 5] 30 [[2, 10], [9, 15], [10, 5], [11, 5]] 5
[3, 2, 7] 20 [[1, 15], [5, 16], [8, 6]] -1
[4, 2, 7] 20 [[1, 15], [5, 16], [8, 6]] -1
[1, 1, 1] 5 [[1, 2], [3, 2]] 3

문제 풀이

이 문제에서 중요한 부분은 모든 시간을 1초씩 확인할 필요가 없다는 것입니다.

몬스터의 공격이 발생하는 시간은 이미 정해져 있으므로,
각 공격과 다음 공격 사이에 존재하는 회복 가능한 시간만 계산하면 됩니다.

1. 공격 사이의 회복 시간 계산

이전 공격 시간이 prev, 현재 공격 시간이 current라고 한다면 두 공격 사이에 실제로 붕대를 감을 수 있는 시간은

current - prev - 1

초입니다.

예를 들어 이전 공격이 2초, 다음 공격이 9초라면

3, 4, 5, 6, 7, 8초

동안 붕대를 감을 수 있으므로 총 6초 동안 회복할 수 있습니다.

현재 공격이 발생하는 9초에는 공격을 받기 때문에 회복할 수 없다는 점에 주의해야 합니다.


2. 기본 회복량 계산

회복 가능한 시간이 healTime초라면 매초 x만큼 회복하므로 기본 회복량은

healTime × x

가 됩니다.

예를 들어 6초 동안 붕대를 감을 수 있고 초당 회복량이 1이라면

6 × 1 = 6

만큼 체력을 회복합니다.


3. 연속 성공 추가 회복 계산

붕대 감기를 t초 연속으로 성공하면 추가로 y만큼 회복할 수 있습니다.

따라서 healTime초 동안 받을 수 있는 추가 회복 횟수는

healTime / t의 몫

으로 구할 수 있습니다.

즉, 추가 회복량은

(healTime // t) × y

가 됩니다.

예를 들어 시전 시간이 5초이고 공격 사이에 11초의 시간이 있다면

  • 5초 연속 성공 → 추가 회복 1회
  • 다시 5초 연속 성공 → 추가 회복 1회
  • 남은 1초 → 추가 회복 없음

이므로 총 2번의 추가 회복을 받을 수 있습니다.


4. 공격 사이의 전체 회복량

따라서 공격 사이에서 회복할 수 있는 전체 체력은

healTime × x + (healTime // t) × y

로 계산할 수 있습니다.

회복한 이후에는 현재 체력이 최대 체력을 넘어갈 수 없으므로

현재 체력과 최대 체력 중 작은 값

으로 체력을 제한해야 합니다.


5. 공격 처리

회복 처리가 끝났다면 현재 공격의 피해량만큼 체력을 감소시킵니다.

이때 체력이 0 이하가 된다면 캐릭터가 죽은 것이므로 바로 -1을 반환합니다.

살아 있다면 현재 공격 시간을 이전 공격 시간으로 저장하고 다음 공격을 처리합니다.

공격을 받는 순간 붕대 감기의 연속 성공 기록이 초기화되기 때문에, 다음 공격 구간에서는 다시 처음부터 연속 시간을 계산하면 됩니다.


전체 흐름

각 공격에 대해 다음 과정을 반복하면 됩니다.

  1. 이전 공격과 현재 공격 사이의 회복 가능한 시간을 구합니다.
  2. 해당 시간 동안의 기본 회복량을 계산합니다.
  3. t초 연속 성공 횟수에 따른 추가 회복량을 계산합니다.
  4. 최대 체력을 넘지 않도록 현재 체력을 조정합니다.
  5. 현재 공격의 피해량을 감소시킵니다.
  6. 체력이 0 이하라면 -1을 반환합니다.
  7. 모든 공격을 버텼다면 마지막 공격 직후의 체력을 반환합니다.

예제 1

bandage = [5, 1, 5]

health = 30

attacks = [[2, 10], [9, 15], [10, 5], [11, 5]]

이라고 해보겠습니다.

처음 체력은 30입니다.

2초 공격

공격 이전에 회복 가능한 시간은 1초입니다.

하지만 이미 최대 체력이므로 체력은 그대로 30입니다.

10의 피해를 받아

30 → 20

이 됩니다.

9초 공격

2초 공격 이후 9초 공격 이전까지

3 ~ 8초

총 6초 동안 회복할 수 있습니다.

기본 회복량은

6 × 1 = 6

이고,

5초 연속 성공을 한 번 달성하므로 추가로 5를 회복합니다.

따라서

20 + 6 + 5 = 31

이지만 최대 체력이 30이므로 현재 체력은 30이 됩니다.

이후 15의 피해를 받아

30 → 15

가 됩니다.

10초 공격

9초와 10초 사이에는 회복할 수 있는 시간이 없습니다.

따라서 바로 5의 피해를 받아

15 → 10

이 됩니다.

11초 공격

마찬가지로 회복할 시간이 없으므로 바로 5의 피해를 받습니다.

10 → 5

모든 공격이 끝난 뒤 체력이 5 남았으므로 정답은 5입니다.


시간 복잡도

공격의 개수를 N이라고 했을 때 각 공격을 한 번씩만 확인합니다.

따라서 시간 복잡도는

O(N)

입니다.

공격 시간의 최대값만큼 매초 시뮬레이션하는 방법도 가능하지만, 공격 사이의 시간을 한 번에 계산하면 불필요한 반복 없이 문제를 해결할 수 있습니다.

def solution(bandage, health, attacks):
    cast_time, heal_per_sec, bonus_heal = bandage
    hp = health
    heal_count = 0
    attack_idx = 0
    
    for t in range(1, attacks[-1][0] + 1):
        # 1. 몬스터 공격 피격
        if t == attacks[attack_idx][0]:
            hp -= attacks[attack_idx][1]
            if hp <= 0:
                return -1
            heal_count = 0
            attack_idx += 1
            continue
            
        # 2. 붕대 감기 (회복)
        heal_count += 1
        hp += heal_per_sec
        
        # 3. 연속 성공 보너스 회복
        if heal_count == cast_time:
            hp += bonus_heal
            heal_count = 0
            
        # 최대 체력 초과 방지
        hp = min(health, hp)
        
    return hp
728x90
반응형
728x90
반응형

백준 서버 종료, 그리고 이후 계획 정리

최근 백준(BOJ) 서버 종료 이슈를 보면서, 그동안 문제 풀이를 해온 흐름을 한 번 정리하게 됐다.

Platinum V 달성

Platinum V는 이전에 이미 달성한 상태다.
당시에는 단순히 티어를 올리는 것보다, 문제 접근 방식 자체가 바뀌는 구간이라는 느낌이 컸다.

구현이나 기본 알고리즘에서 벗어나서
문제를 어떻게 쪼개고, 어떤 기준으로 해결할지를 먼저 정리하는 쪽으로 풀이 방식이 바뀌었다.

백준 이후

서버 종료 이슈가 있긴 했지만,
사실 플랫폼 자체보다는 문제 풀이 경험이 더 중요한 부분이라 큰 영향은 없다.

다만, 자연스럽게 다음 단계로 넘어갈 시점이라고 판단해서
플랫폼을 프로그래머스로 옮겨서 계속 진행할 예정이다.

앞으로의 문제 풀이 방향

앞으로는 프로그래머스를 중심으로 문제를 풀 계획이다.

백준이 알고리즘 중심이었다면,
프로그래머스는 SQL이나 실무형 문제 비중이 있어서 방향을 바꾸기에 적절하다고 생각했다.

언어 변경

기존에는 Python 위주로 풀이를 했지만,
앞으로는 아래 두 가지에 집중할 예정이다.

  • Java
  • SQL

Java는 구조적으로 코드를 짜는 연습을 하기 좋고,
SQL은 데이터 처리 쪽 감각을 키우기 위해서 선택했다.

정리

  • Platinum V: 이전에 달성 완료
  • 플랫폼: 백준 → 프로그래머스
  • 언어: Python → Java, SQL 중심

큰 방향만 정리하면 이 정도다.
앞으로는 문제 풀이 방식 자체를 조금 더 실무 쪽으로 가져가는 데 집중할 예정이다.

728x90
반응형
728x90
반응형

1202번 보석 도둑 (골드 2)

1. 문제
세계적인 도둑 상덕이는 보석점을 털기로 결심했다.

상덕이가 털 보석점에는 보석이 총 N개 있다. 
각 보석은 무게 Mi와 가격 Vi를 가지고 있다. 
상덕이는 가방을 K개 가지고 있고, 각 가방에 담을 수 있는 최대 무게는 Ci이다. 
가방에는 최대 한 개의 보석만 넣을 수 있다.

상덕이가 훔칠 수 있는 보석의 최대 가격을 구하는 프로그램을 작성하시오.

2. 입력
첫째 줄에 N과 K가 주어진다. (1 ≤ N, K ≤ 300,000)
다음 N개 줄에는 각 보석의 정보 Mi와 Vi가 주어진다. (0 ≤ Mi, Vi ≤ 1,000,000)
다음 K개 줄에는 가방에 담을 수 있는 최대 무게 Ci가 주어진다. (1 ≤ Ci ≤ 100,000,000)
모든 숫자는 양의 정수이다.

3. 출력
첫째 줄에 상덕이가 훔칠 수 있는 보석 가격의 합의 최댓값을 출력한다.

4. 문제 풀이
DP를 이용한 가방 문제와 비슷하다고 생각할 수 있으나 다른점이 여러개 존재한다.
일단은, 가방이 1개가 아닌 여러개 이며, 각각의 가방에 넣을 수 있는 용량이 다 다르다는 것.
또한 하나의 가방에 하나의 보석만 넣을 수 있다는 것이다.

그렇다면, 최대 힙을 이용한 정렬을 이용해서 문제를 풀어야 한다.

1) 가방을 용량 기준, 보석을 무게 기준 오름차순 정렬
2) 가방의 용량 순서대로, 해당 가방에 넣을 수 있는 보석 확인
- 해당 보석의 가격을 최대 힙으로 삽입
- 해당 보석이 저장된 최소 힙에서 삭제
3) 해당 가방에 담을 수 있는 보석들 중 가장 비싼 가격을 최대 힙에서 추출

>>>코드

"""
1202번 보석 도둑 (골드 2)
input : 
    n 보석 개수, k 가방 개수
    m 보석 무게, v 보석 가격
    c 가방 최대무게
output :
    weight 보석 가격의 최댓값
- 각 가방에 1개의 보석만 담는 것이 가능
>> 각 가방에 넣을 수 있는 무게보다 작은 보석들 중
가장 가격이 비싼 것 찾기

보석을 최소 힙에 넣어서 최소 무게 값이 루트로 오도록 설정
해당 가방에 들어갈 수 있는 모든 보석의 가격을 최대 힙에 삽입
해당 가방에 들어갈 수 있는 최대 가격을 최대 힙에서 추출 
"""
import heapq

n, k = map(int, input().split())
gem = [tuple(map(int, input().split())) for _ in range(n)]
bag = [int(input()) for _ in range(k)]
gem.sort() # 무게 기준으로 오름차순 정렬
bag.sort() # 가방 용량기준 오름차순 정렬

result = 0
gem_list = []
# 용량이 작은 가방 부터 가능한 보석 넣기
for c in bag:
    # 해당 가방에 넣을 수 있는 보석 모두 넣기
    while gem and gem[0][0] <= c:
        # 가격을 최대 힙으로 저장
        heapq.heappush(gem_list, -gem[0][1])
        # 넣은 보석은 원래 리스트에서 제외
        heapq.heappop(gem)
    # 넣을 수 있는 보석들 중에 가장 비싼 보석 확인
    if gem_list:
        result -= heapq.heappop(gem_list)
print(result)


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

728x90
반응형
728x90
반응형

2252번 줄 세우기 (골드 3)

1. 문제
N명의 학생들을 키 순서대로 줄을 세우려고 한다. 
각 학생의 키를 직접 재서 정렬하면 간단하겠지만, 
마땅한 방법이 없어서 두 학생의 키를 비교하는 방법을 사용하기로 하였다. 
그나마도 모든 학생들을 다 비교해 본 것이 아니고, 일부 학생들의 키만을 비교해 보았다.

일부 학생들의 키를 비교한 결과가 주어졌을 때, 줄을 세우는 프로그램을 작성하시오.

2. 입력
첫째 줄에 N(1 ≤ N ≤ 32,000), M(1 ≤ M ≤ 100,000)이 주어진다. 
M은 키를 비교한 횟수이다. 
다음 M개의 줄에는 키를 비교한 두 학생의 번호 A, B가 주어진다. 
이는 학생 A가 학생 B의 앞에 서야 한다는 의미이다.

학생들의 번호는 1번부터 N번이다.

3. 출력
첫째 줄에 학생들을 앞에서부터 줄을 세운 결과를 출력한다. 
답이 여러 가지인 경우에는 아무거나 출력한다.

4. 문제 풀이
위상 정렬 문제이다.

1) 위상정렬이란?
특정 노드 도달 전에 무조건 선행되어야 하는 노드가 존재하는 노드들의 순서 정렬이다.
- 반드시 방향 사이클이 없는 그래프에서만 가능하다. (방향성과 비순환성 충족)

2) 위상정렬 알고리즘
진입 차수와 큐를 이용해서 알고리즘을 만들 수 있다.

  • 초기화 : 모든 노드의 진입 차수를 계산
  • 큐(Queue) 삽입 : 진입 차수가 0인 노드(먼저 할 일이 없는 노드)를 모두 큐에 넣는다.
  • 큐가 빌때까지 반복
    • 큐에서 노드를 꺼내 정렬 결과 리스트에 추가
    • 해당 노드에서 나가는 모든 간선 삭제 (연결된 노드들의 진입 차수 -1)
    • 진입 차수가 새롭게 0이 된 노드를 큐에 넣기


>> 코드

"""
2252번 줄 세우기 (골드 3)
input : 
    n 학생의 수, m 키를 비교한 횟수
    a, b 학생의 번호
output :
    order 키순서
"""
from collections import deque

n, m = map(int, input().split())
indegree = [0] * (n+1)
order = [[] for _ in range(n+1)]
for i in range(m):
    a, b = map(int, input().split())
    order[a].append(b)
    indegree[b] += 1

def height_order():
    result = []
    queue = deque()
    
    for i in range(1, n+1):
        if indegree[i] == 0:
            queue.append(i)
    
    while queue:
        now = queue.popleft()
        result.append(now)
        
        for next_node in order[now]:
            indegree[next_node] -= 1
            if indegree[next_node] == 0:
                queue.append(next_node)
        
    return result

print(*height_order())


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

728x90
반응형
728x90
반응형

1005번 ACM Craft (골드 3)

1. 문제
서기 2012년! 드디어 2년간 수많은 국민들을 기다리게 한 게임 
ACM Craft (Association of Construction Manager Craft)가 발매되었다.

이 게임은 지금까지 나온 게임들과는 다르게 ACM크래프트는 
다이나믹한 게임 진행을 위해 건물을 짓는 순서가 정해져 있지 않다. 
즉, 첫 번째 게임과 두 번째 게임이 건물을 짓는 순서가 다를 수도 있다. 
매 게임시작 시 건물을 짓는 순서가 주어진다. 
또한 모든 건물은 각각 건설을 시작하여 완성이 될 때까지 Delay가 존재한다.

 
위의 예시를 보자.

이번 게임에서는 다음과 같이 건설 순서 규칙이 주어졌다. 
1번 건물의 건설이 완료된다면 2번과 3번의 건설을 시작할수 있다. 
(동시에 진행이 가능하다) 
그리고 4번 건물을 짓기 위해서는 2번과 3번 건물이 모두 건설 완료되어야지만 
4번건물의 건설을 시작할수 있다.

따라서 4번건물의 건설을 완료하기 위해서는 우선 처음 1번 건물을 건설하는데 10초가 소요된다. 
그리고 2번 건물과 3번 건물을 동시에 건설하기 시작하면 2번은 1초뒤에 건설이 완료되지만 
아직 3번 건물이 완료되지 않았으므로 4번 건물을 건설할 수 없다. 
3번 건물이 완성되고 나면 그때 4번 건물을 지을수 있으므로 4번 건물이 완성되기까지는 총 120초가 소요된다.

프로게이머 최백준은 애인과의 데이트 비용을 마련하기 위해 서강대학교배 ACM크래프트 대회에 참가했다! 
최백준은 화려한 컨트롤 실력을 가지고 있기 때문에 모든 경기에서 특정 건물만 짓는다면 무조건 게임에서 이길 수 있다. 
그러나 매 게임마다 특정건물을 짓기 위한 순서가 달라지므로 최백준은 좌절하고 있었다. 
백준이를 위해 특정건물을 가장 빨리 지을 때까지 걸리는 최소시간을 알아내는 프로그램을 작성해주자.

2. 입력
첫째 줄에는 테스트케이스의 개수 T가 주어진다. 
각 테스트 케이스는 다음과 같이 주어진다. 
첫째 줄에 건물의 개수 N과 건물간의 건설순서 규칙의 총 개수 K이 주어진다. 
(건물의 번호는 1번부터 N번까지 존재한다) 

둘째 줄에는 각 건물당 건설에 걸리는 시간 D1, D2, ..., DN이 공백을 사이로 주어진다. 
셋째 줄부터 K+2줄까지 건설순서 X Y가 주어진다. 
(이는 건물 X를 지은 다음에 건물 Y를 짓는 것이 가능하다는 의미이다) 

마지막 줄에는 백준이가 승리하기 위해 건설해야 할 건물의 번호 W가 주어진다.

3. 출력
건물 W를 건설완료 하는데 드는 최소 시간을 출력한다. 
편의상 건물을 짓는 명령을 내리는 데는 시간이 소요되지 않는다고 가정한다.

건설순서는 모든 건물이 건설 가능하도록 주어진다.

4. 제한
- 2 ≤ N ≤ 1000
- 1 ≤ K ≤ 100,000
- 1 ≤ X, Y, W ≤ N
- 0 ≤ Di ≤ 100,000, Di는 정수

5. 문제 풀이
DP와 위상정렬을 이용한 문제이다.

DP로는 해당 번호의 건물을 세우는데 소요되는 시간을 저장한다.
위상정렬로는 진입차수를 이용하여 건설 순서를 세운다.

>>>코드

"""
1005번 ACM Craft (골드 3)
input : 
    t 테스트 케이스
    n 건물의 수, k 건물 규칙의 수
    time 각 건물당 건설에 걸리는 시간
    x, y 건설 순서
    w 건설해야하는 건물 번호
output :
    min_time 건설 완료하는데 드는 최소 시간
"""
from collections import deque

t = int(input())
for _ in range(t):
    n, k = map(int, input().split())
    time = [0] + list(map(int, input().split()))
    
    # 진입 차수 및 그래프 저장
    indegree = [0] * (n+1)
    graph = [[] for _ in range(n+1)]
    for i in range(k):
        x, y = map(int, input().split())
        graph[x].append(y)
        indegree[y] += 1
    # 도착지점
    w = int(input())
    
    # 해당 노드에 도달하는데까지 필요한 시간
    dp = [0] * (n+1)
    
    # 위상정렬 시작
    queue = deque()
    for i in range(1, n+1):
        if indegree[i] == 0:
            queue.append(i)
            dp[i] = time[i]
    
    while queue:
        now = queue.popleft()
        
        for next_node in graph[now]:
            dp[next_node] = max(dp[next_node], dp[now] + time[next_node])
            indegree[next_node] -= 1
            
            if indegree[next_node] == 0:
                queue.append(next_node)

    print(dp[w])


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

728x90
반응형
728x90
반응형

2623번 음악프로그램 (골드 3)

1. 문제
인터넷 방송 KOI(Korea Open Internet)의 음악 프로그램 PD인 남일이는 
자기가 맡은 프로그램 '뮤직 KOI'에서 가수의 출연 순서를 정하는 일을 매우 골치 아파한다. 
순서를 정하기 위해서는 많은 조건을 따져야 한다.

그래서 오늘 출연 예정인 여섯 팀의 가수에 대해서 남일이가 보조 PD 세 명에게 
각자 담당한 가수의 출연 순서를 정해오게 하였다. 보조 PD들이 가져온 것은 아래와 같다.

- 1 4 3
- 6 2 5 4
- 2 3

첫 번째 보조 PD는 1번 가수가 먼저, 다음에 4번 가수, 다음에 3번 가수가 출연하기로 순서를 정했다. 
두 번째 보조 PD는 6번, 2번, 5번, 4번 순으로 자기 담당 가수들의 순서를 정했다. 
한 가수를 여러 보조 PD가 담당할 수도 있다. 
마지막으로, 세 번째 보조 PD는 2번 먼저, 다음에 3번으로 정했다.

남일이가 할 일은 이 순서들을 모아서 전체 가수의 순서를 정하는 것이다. 
남일이는 잠시 생각을 하더니 6 2 1 5 4 3으로 순서를 정했다. 
이렇게 가수 순서를 정하면 세 보조 PD가 정해온 순서를 모두 만족한다. 
물론, 1 6 2 5 4 3으로 전체 순서를 정해도 괜찮다.

경우에 따라서 남일이가 모두를 만족하는 순서를 정하는 것이 불가능할 수도 있다. 
예를 들어, 세 번째 보조 PD가 순서를 2 3 대신에 3 2로 정해오면 남일이가 전체 순서를 정하는 것이 불가능하다. 이번에 남일이는 우리 나라의 월드컵 4강 진출 기념 음악제의 PD를 맡게 되었는데, 출연 가수가 아주 많다. 
이제 여러분이 해야 할 일은 보조 PD들이 가져 온 순서들을 보고 
남일이가 가수 출연 순서를 정할 수 있도록 도와 주는 일이다.

보조 PD들이 만든 순서들이 입력으로 주어질 때, 전체 가수의 순서를 정하는 프로그램을 작성하시오.

2. 입력
첫째 줄에는 가수의 수 N과 보조 PD의 수 M이 주어진다. 
가수는 번호 1, 2,…,N 으로 표시한다. 
둘째 줄부터 각 보조 PD가 정한 순서들이 한 줄에 하나씩 나온다. 
각 줄의 맨 앞에는 보조 PD가 담당한 가수의 수가 나오고, 그 뒤로는 그 가수들의 순서가 나온다. 
N은 1이상 1,000이하의 정수이고, M은 1이상 100이하의 정수이다.

3. 출력
출력은 N 개의 줄로 이뤄지며, 한 줄에 하나의 번호를 출력한다. 
이들은 남일이가 정한 가수들의 출연 순서를 나타낸다. 
답이 여럿일 경우에는 아무거나 하나를 출력 한다. 
만약 남일이가 순서를 정하는 것이 불가능할 경우에는 첫째 줄에 0을 출력한다.

4. 문제 풀이
위상정렬 문제이다.
다만 데이터가 위상 정렬이 가능한 경우만 존재하는 것이 아니라
위상 정렬이 불가능한 경우도 주어져 해당 예외를 처리하는 것이 더해진 문제이다.

마지막 result의 길이로 해당 판단이 가능하다.

>>>코드

"""
2623번 음악프로그램 (골드 3)
input : 
    n 가수의 수, m 보조 PD의 수
    graph 노래 순서
output :
    sing_order 최종 노래 순서
"""
from collections import deque

n, m = map(int, input().split())

indegree = [0] * (n+1)
graph = [[] for _ in range(n+1)]
for _ in range(m):
    temp = list(map(int, input().split()))
    amount = temp[0]
    for i in range(1, amount):
        graph[temp[i]].append(temp[i+1])
        indegree[temp[i+1]] += 1

def sing_order(graph, indegree, n):
    queue = deque()
    result = []

    for i in range(1, n+1):
        if indegree[i] == 0:
            queue.append(i)

    while queue:
        now = queue.popleft()
        result.append(now)
        
        for next_node in graph[now]:
            indegree[next_node] -= 1
                
            if indegree[next_node] == 0:
                queue.append(next_node)
    if len(result) == n:
        return result
    else:
        return [0]
    
result = sing_order(graph, indegree, n)
for singer in result:
    print(singer)



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

728x90
반응형

+ Recent posts