문제
집에서 시간을 보내던 오영식은 박성원의 부름을 받고 급히 달려왔다. 박성원이 캠프 때 쓸 N개의 랜선을 만들어야 하는데 너무 바빠서 영식이에게 도움을 청했다.
이미 오영식은 자체적으로 K개의 랜선을 가지고 있다. 그러나 K개의 랜선은 길이가 제각각이다. 박성원은 랜선을 모두 N개의 같은 길이의 랜선으로 만들고 싶었기 때문에 K개의 랜선을 잘라서 만들어야 한다. 예를 들어 300cm 짜리 랜선에서 140cm 짜리 랜선을 두 개 잘라내면 20cm는 버려야 한다. (이미 자른 랜선은 붙일 수 없다.)
편의를 위해 랜선을 자르거나 만들 때 손실되는 길이는 없다고 가정하며, 기존의 K개의 랜선으로 N개의 랜선을 만들 수 없는 경우는 없다고 가정하자. 그리고 자를 때는 항상 센티미터 단위로 정수길이만큼 자른다고 가정하자. N개보다 많이 만드는 것도 N개를 만드는 것에 포함된다. 이때 만들 수 있는 최대 랜선의 길이를 구하는 프로그램을 작성하시오.
입력
첫째 줄에는 오영식이 이미 가지고 있는 랜선의 개수 K, 그리고 필요한 랜선의 개수 N이 입력된다. K는 1이상 10,000이하의 정수이고, N은 1이상 1,000,000이하의 정수이다. 그리고 항상 K ≦ N 이다. 그 후 K줄에 걸쳐 이미 가지고 있는 각 랜선의 길이가 센티미터 단위의 정수로 입력된다. 랜선의 길이는 231-1보다 작거나 같은 자연수이다.
출력
첫째 줄에 N개를 만들 수 있는 랜선의 최대 길이를 센티미터 단위의 정수로 출력한다.
예제 입력
4 11
802
743
457
539
예제 출력
200
힌트
802cm 랜선에서 4개, 743cm 랜선에서 3개, 457cm 랜선에서 2개, 539cm 랜선에서 2개를 잘라내 모두 11개를 만들 수 있다.
처음 이 문제를 브루트 포스로 풀어보려고 시도했습니다 시간초과가 발생했습니다
import sys
K,N = map(int, input().split())
numArr = []
for _ in range(K):
numArr.append(int(sys.stdin.readline().strip()))
# print(numArr)
totalSum = sum(numArr)
accNum = 0
P = N
dividedNum = 0
while True:
dividedNum = totalSum // P
for i in numArr:
accNum += (i // dividedNum)
if accNum >= N:
break
else:
accNum = 0
P += 1
while True:
tmpNum = 0
for i in numArr:
tmpNum += (i // dividedNum)
if tmpNum >= N:
dividedNum += 1
else:
dividedNum -= 1
break
print(dividedNum)
하지만 이렇게 풀었을 때 52%까지는 해결되었지만 시간초과 발생하였습니다. 문제를 푸는데 브루트포스로는 시간제한을 해결할 수 없었습니다. 그래서 다른 방식을 생각했습니다
해결방법은 이분탐색으로 접근해보는 것입니다
각 자리의 값을 나눌 수 있는 값은 1~주어진 수 중에 가장 큰 수까지 입력 받은 랜선들을 자를 수 있는 수들 입니다.
이분탐색의 기본접근법은 리스트가 있을 때 가장 작은 값과 가장 큰 값을 더해서 중간값을 만들고 해당 중간값을 이용해 값을 찾되 그보다 더 적으면 인덱스의 범위를 가장 작은 값~ 중간값-1로, 그보다 더 큰 인덱스의 범위 안에 있다면 중간값+1 ~ 가장 큰 값의 인덱스 범위에서 수를 찾는 형태입니다.
위 형태로 풀게 된다면 while문을 2개 써가면서 K개를 충족하는 수들을 찾고 그 중에서 높은 값을 찾기 위해서 while문을 돌릴 필요가 없이 이분탐색으로 찾게 됩니다.
이 문제를 풀때 나눌 수 있는 값은 1 ~ Max(Arr)의 리스트가 있다고 가정하여서 그 중에서 충족하는 값을 찾기 위한 이분탐색을 하고 거기서 나오는 중간값을 통해서 K개를 충족하는 지 확인합니다. 그리고나서 리스트의 범위를 줄여가며 계속 탐색하는 알고리즘입니다
코드는 다음과 같습니다
import sys
K,N = map(int, input().split())
numArr = []
for _ in range(K):
numArr.append(int(sys.stdin.readline().strip()))
totalSum = sum(numArr)
start = 1
end = max(numArr)
while start <= end:
mid = (start + end) // 2
cnt = 0
for i in numArr:
cnt += i // mid
if cnt >= N:
start = mid + 1
else:
end = mid - 1
print(end)
//을 이용해서 나머지 떨어지는 값들은 버리고 나오는 몫 만을 사용합니다. 최대 랜선 길이를 출력하므로 end를 출력합니다
위 예제를 콘솔에서 각각의 start, end, mid값을 출력했을 때 결과는 다음과 같습니다.
계속 해당하는 범위를 찾게 되며 마지막에 mid값이 201일때 end를 mid - 1을 하게되어 200이 최대 값으로 출력됩니다.
'알고리즘 > 문제풀이' 카테고리의 다른 글
백준 1874번 스택 수열 - Python (0) | 2023.03.03 |
---|---|
백준 10773번 제로 - Python (1) | 2023.03.03 |
백준 15829번 Hashing - Python (0) | 2023.03.02 |
백준 2292번 벌집문제 - Python (0) | 2023.02.27 |
백준 1018번 체스판 다시 칠하기 - Python (0) | 2023.02.27 |