
많은 조건을 꼼꼼히 따져봐야 풀 수 있는 문제였다. 다른 분이 올려주신 반례 모음집이 문제를 푸는 데 많은 도움이 됐다.
발상 자체는 단순하다. 누를 수 있는 숫자로 최대한 목표 채널과 가까운 값을 만든 후 증감 버튼으로 조정하는 횟수를 세면 된다. 총 5가지의 경우를 생각해 보았다.
예를 들어 목표 채널이 5457이고 6, 7, 8번이 고장났다면 2는 5455, 3은 5459, 4는 10000, 5는 999이다.
2와 3을 구할 때는 일단 목표 채널과 동일한 숫자를 지정하다가, 어떤 숫자가 고장나서 동일한 값을 쓸 수 없을 때 근삿값을 대신 넣고 해당 근삿값의 대소 여부에 따라 나머지 자릿수를 채운다. 근삿값 자체가 범위(0~9)를 벗어나 넣을 수 없는 상황이라면 앞 자릿수로 이동하여 근삿값을 구하는 것을 반복한다.
1~5에 해당하는 값 중 결과를 최소로 만드는 것을 출력해주면 된다.
import sys
input = sys.stdin.readline
broken = [False] * 10
N = int(input())
M = int(input())
if M > 0:
for i in list(map(int, input().split())):
broken[i] = True
MAX = 0
MIN = 0
for i in range(9, -1, -1):
if not broken[i]:
MAX = i
break
for i in range(1, 10, 1):
if not broken[i]:
MIN = i
break
def count_channel():
if N == 100:
return 0
if M == 10:
return abs(N - 100)
upper_bound = "" # N보다 1자리 큰 수 중에서 최솟값
lower_bound = "" # N보다 1자리 작은 수 중에서 최댓값
if not broken[0]:
upper_bound = str(MIN) + "0" * len(str(N))
else:
upper_bound = str(MIN) * (len(str(N)) + 1)
if len(str(N)) > 1:
lower_bound = str(MAX) * (len(str(N)) - 1)
else:
lower_bound = "0" if not broken[0] else str(MIN)
bound = min(
len(upper_bound) + abs(N - int(upper_bound)),
len(lower_bound) + abs(N - int(lower_bound)),
)
res_upper = ""
res_lower = ""
for d in str(N):
if broken[int(d)]:
break
res_upper += d
res_lower += d
# 상한값 계산
idx = len(res_upper) if res_upper else 0
flag = True
while flag and 0 <= idx < len(str(N)):
for i in range(int(str(N)[idx]) + 1, 10):
if not broken[i]:
res_upper = res_upper[:idx] + str(i)
while len(res_upper) < len(str(N)):
res_upper += "0" if not broken[0] else str(MIN)
flag = False
break
idx -= 1
if not res_upper:
if not broken[0]:
res_upper = str(MIN) + "0" * len(str(N))
else:
res_upper = str(MIN) * (len(str(N)) + 1)
# 하한값 계산
idx = len(res_lower) if res_lower else 0
flag = True
while flag and 0 <= idx < len(str(N)):
for i in range(int(str(N)[idx]) - 1, -1, -1):
if not broken[i]:
res_lower = res_lower[:idx] + str(i)
while len(res_lower) < len(str(N)):
res_lower += str(MAX)
flag = False
break
idx -= 1
if not res_lower:
if len(str(N)) > 1:
res_lower = str(MAX) * (len(str(N)) - 1)
else:
res_lower = "0" if not broken[0] else str(MIN)
return min(
abs(N - 100),
bound,
len(str(int(res_upper))) + abs(N - int(res_upper)),
len(str(int(res_lower))) + abs(N - int(res_lower)),
)
print(count_channel())
그동안 풀었던 PS 문제 코드들을 깃허브에 정리하다가 우연히 이 문제를 다시 보게 되었는데, 위처럼 일일이 모든 경우에 대한 처리 방법을 구현하지 않고도 브루트 포스 기법으로 매우 단순하게 풀 수 있음을 깨닫게 되었다.
이동하려는 채널의 범위가 최대 50만이고, 그 위쪽에서 감소 버튼을 통해 내려가는 경우를 고려하기 위해 가능한 채널의 범위를 최대 100만까지로 잡는다. 그리고 각 채널의 자릿수를 하나씩 보면서 고장난 버튼인지 아닌지를 판단하므로 가장 긴 자릿수는 100만 = 1000000의 7자리가 된다.
즉, 1000000 * 7 < 10000000이므로 다른 연산들을 고려하더라도 제한 시간 2초 안에 충분히 해결할 수 있다. (왜 작년엔 브루트 포스로 풀 생각을 못 했을까...?)
import sys
input = sys.stdin.readline
N = int(input())
M = int(input())
DIGIT = set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
if M == 0: # 고장난 숫자가 없다면 한 번에 이동
print(min(len(str(N)), abs(N - 100)))
exit(0)
brokenDigit = set(input().rstrip().split())
result = abs(N - 100)
if M == 10: # 모든 숫자가 고장났다면 증감 버튼을 통해서만 이동
print(result)
exit(0)
for inter in range(1000001):
reachable = True
for digit in str(inter):
if digit in brokenDigit: # 현재 채널 값에 고장난 숫자가 포함된 경우
reachable = False
break
if reachable:
result = min(result, len(str(inter)) + abs(inter - N)) # 숫자 버튼 누르기 + 증감버튼 누르기
print(result)