자동차 실내온도를 제어하는 에어컨 시스템이 있다.
현재 0분의 실내온도는 실외온도와 같다.
승객이 탑승 중인 시간에는 실내온도가 항상 쾌적 범위인 t1 ~ t2 사이에 있어야 한다.
에어컨은 켜거나 끌 수 있고, 켜져 있을 때는 희망온도를 설정할 수 있다.
에어컨 동작 규칙은 다음과 같다.
실내온도와 희망온도가 다르면 1분 뒤 희망온도 방향으로 1도 이동한다.
현재온도 < 희망온도 -> 1도 상승
현재온도 > 희망온도 -> 1도 하강
이때 소비 전력은 a다.
실내온도와 희망온도가 같다면 온도는 유지된다.
이때 소비 전력은 b다.
실내온도는 실외온도 방향으로 매 분 1도씩 이동한다.
실내온도와 실외온도가 같다면 변하지 않는다.
에어컨이 꺼져 있으면 전력은 소비하지 않는다.
목표는 승객이 탑승 중인 시간에 실내온도를 쾌적 범위로 유지하면서 소비 전력의 최솟값을 구하는 것이다.
이 문제는 시간에 따라 실내온도가 변한다.
따라서 다음과 같은 DP를 생각할 수 있다.
dp[time][temp]
의미는 다음과 같다.
time분에 실내온도가 temp일 때의 최소 소비 전력
각 분마다 가능한 실내온도에서 다음 분의 실내온도로 이동하는 경우를 모두 확인한다.
이동 방법은 크게 두 가지다.
에어컨을 켠 경우는 다시 다음 세 가지로 볼 수 있다.
프로그래머스 원문 제한에서 온도는 보통 -10도부터 40도 범위 안에서 다룰 수 있다.
따라서 DP 배열의 온도 범위를 다음처럼 잡는다.
MIN_TEMP = -10
MAX_TEMP = 40
파이썬 리스트 인덱스는 음수를 그대로 쓰기 어렵기 때문에 OFFSET을 더해 저장한다.
OFFSET = 10
예를 들어 실제 온도 -10은 인덱스 0이 된다.
-10 + 10 = 0
실제 온도 28은 인덱스 38이 된다.
28 + 10 = 38
0분의 실내온도는 항상 실외온도와 같다.
dp[0][temperature + OFFSET] = 0
아직 전력을 쓰지 않았으므로 비용은 0이다.
승객이 탑승하지 않은 시간에는 실내온도가 어떤 값이어도 상관없다.
하지만 승객이 탑승 중인 시간에는 반드시 t1 ~ t2 사이여야 한다.
이를 함수로 만들면 다음과 같다.
def is_comfortable(temp, time):
if onboard[time] == 0:
return True
return t1 <= temp <= t2
다음 시간으로 이동할 때, 이동 후 온도가 해당 시간의 조건을 만족하는지 확인해야 한다.
에어컨을 끄면 실내온도는 실외온도 방향으로 1도 이동한다.
if temp < temperature:
next_temp = temp + 1
elif temp > temperature:
next_temp = temp - 1
else:
next_temp = temp
에어컨이 꺼져 있으므로 추가 비용은 없다.
next_cost = current_cost
에어컨이 켜져 있고 희망온도와 현재 실내온도가 같으면 온도는 유지된다.
next_temp = temp
이때 소비 전력은 b다.
next_cost = current_cost + b
에어컨이 켜져 있고 희망온도와 현재 실내온도가 다르면 실내온도는 희망온도 방향으로 1도 이동한다.
따라서 다음 온도는 두 가지가 가능하다.
temp - 1
temp + 1
이때 소비 전력은 a다.
next_cost = current_cost + a
def solution(temperature, t1, t2, a, b, onboard):
MIN_TEMP = -10
MAX_TEMP = 40
OFFSET = 10
INF = 10**15
size = MAX_TEMP - MIN_TEMP + 1
time_count = len(onboard)
dp = [
[INF] * size
for _ in range(time_count)
]
dp[0][temperature + OFFSET] = 0
def is_comfortable(temp, time):
if onboard[time] == 0:
return True
return t1 <= temp <= t2
for time in range(time_count - 1):
for temp_index in range(size):
current_cost = dp[time][temp_index]
if current_cost == INF:
continue
temp = temp_index - OFFSET
# 1. 에어컨을 끄는 경우
if temp < temperature:
next_temp = temp + 1
elif temp > temperature:
next_temp = temp - 1
else:
next_temp = temp
if MIN_TEMP <= next_temp <= MAX_TEMP:
if is_comfortable(next_temp, time + 1):
next_index = next_temp + OFFSET
dp[time + 1][next_index] = min(
dp[time + 1][next_index],
current_cost,
)
# 2. 에어컨을 켜고 현재 온도를 유지하는 경우
next_temp = temp
if is_comfortable(next_temp, time + 1):
next_index = next_temp + OFFSET
dp[time + 1][next_index] = min(
dp[time + 1][next_index],
current_cost + b,
)
# 3. 에어컨을 켜고 온도를 1도 낮추거나 높이는 경우
for next_temp in (temp - 1, temp + 1):
if not (MIN_TEMP <= next_temp <= MAX_TEMP):
continue
if not is_comfortable(next_temp, time + 1):
continue
next_index = next_temp + OFFSET
dp[time + 1][next_index] = min(
dp[time + 1][next_index],
current_cost + a,
)
return min(dp[-1])
현재 가장 적은 전력을 쓰는 선택이 나중에도 항상 좋은 선택은 아니다.
예를 들어 당장은 에어컨을 끄는 것이 비용이 0이라 좋아 보일 수 있다.
하지만 나중에 승객이 탑승하는 시점에 쾌적 범위를 맞추기 위해 더 많은 전력을 써야 할 수 있다.
반대로 미리 온도를 조절해두면 이후 전력 사용을 줄일 수 있다.
따라서 각 시간과 온도 상태별 최소 비용을 모두 관리하는 DP가 필요하다.
시간의 길이를 T, 온도 범위를 R이라고 하자.
각 시간마다 가능한 온도 상태를 모두 확인한다.
각 상태에서 확인하는 전이는 상수 개수다.
O(T × R)
온도 범위는 -10부터 40까지이므로 R = 51이다.
따라서 사실상 O(T)에 가깝다.
DP 배열은 시간과 온도 범위만큼 사용한다.
O(T × R)
온도 범위가 작기 때문에 충분히 가볍다.
이 문제는 시간에 따라 실내온도가 변하는 상태 전이 문제다.
핵심은 다음과 같다.
dp[time][temp]를 현재 시간과 온도에서의 최소 소비 전력으로 정의한다.b가 든다.a가 든다.t1 ~ t2 범위만 허용한다.각 시간마다 가능한 모든 온도를 관리하면 최소 소비 전력을 안정적으로 구할 수 있다.