import sys
sys.setrecursionlimit(20000)
def solution(k, num, links):
left = sum(num)//k
right = sum(num)
N = len(num)
degrees = [0 for _ in range(N)]
for ind in range(N):
if links[ind][0] != -1:
degrees[links[ind][0]] += 1
if links[ind][1] != -1:
degrees[links[ind][1]] += 1
root = -1
for ind in range(N):
if not degrees[ind]:
root = ind
break
INF = float('inf')
def check(node):
nonlocal links,INF,dp,mid
if links[node][0] == -1 and links[node][1] == -1:
if num[node]<=mid:
dp[node][0] = num[node]
dp[node][1] = 1
else:
dp[node][0] = 0
dp[node][1] = INF
else:
for ind in range(2):
if links[node][ind] != -1:
check(links[node][ind])
left_node = links[node][0]
right_node = links[node][1]
if dp[left_node][0] + dp[right_node][0] + num[node] <= mid:
dp[node][0] = dp[left_node][0] + dp[right_node][0] + num[node]
dp[node][1] = dp[left_node][1] + dp[right_node][1] -1
return
if right_node == -1:
if num[node] <= mid:
dp[node][0] = num[node]
dp[node][1] = dp[left_node][1] + 1
else:
dp[node][0] = 0
dp[node][1] = INF
else:
if num[node] + min(dp[right_node][0],dp[left_node][0])<= mid:
dp[node][0] = num[node] + min(dp[right_node][0],dp[left_node][0])
dp[node][1] = dp[right_node][1] + dp[left_node][1]
elif num[node] <= mid:
dp[node][0] = num[node]
dp[node][1] = dp[left_node][1] + dp[right_node][1] + 1
else:
dp[node][0] = 0
dp[node][1] = INF
while left+1<right:
mid = (left+right)//2
dp = [[0,1] for _ in range(N+1)]
check(root)
if dp[root][1]<=k:
right = mid
else:
left = mid
return right
풀기 힘들었던 문제이다.
이 문제는 트리 DP와 파라메트릭 서치를 활용한 문제로
k개의 그룹으로 나눌 수 있는 최소 값을 찾는것이다.
카카오 해설에서도 알수 있듯이 이분탐색으로 찾으면 되고,
이 이분 탐색의 중간값인 mid가 의미하는 것은 이 트리를 나눌 최대의 인원을 의미한다.
한 그룹의 인원이 mid가 넘어가지 않게 해주면 된다.
즉 mid의 값이 크면 k는 적게 나올것이고, mid의 값이 작으면 k보다 많게 나올 것이다.
우리는 left+1<right일때 끝나고
left일때 false이므로,
우리가 구하고자 하는 값은 right임을 알 수 있다.
문제에 들어가면,
가장 밑 노드에서부터 해주면 된다.
먼저 dp 테이블은 N*2의 크기로 해주었고,
dp[x][0] 은 x 노드에서의 최소 그룹인원의 수
dp[x][1] 은 x 노드에서의 그룹의 개수를 의미한다.
각 노드는 처음에 하나의 그룹이므로, dp[x][0]은 0으로 dp[x][1]은 1로 초기화를 해주었다.
총 4가지 경우가 있을것이다.
1. 자식노드 2개에 누적된 그룹인원과 현재노드의 인원과 더해도 mid보다 작거나 같다.
그러면 dp[parent][0] = num[parent] + dp[left_node][0] + dp[right_node][1]이 될것이다.
그리고 그룹의 수는 자식노드의 그룹의 수를 더해준 것에서 -1을 해준다.
이러는 이유는 우리가 각 노드를 하나의 그룹으로 생각했기 때문에, 처음에 1로 전부 초기화 되어 있어서 그런다.
자식노드와 현재그룹을 더해서 한개의 그룹이 된 것이므로 - 1을 해준다.
2. 자식노드 둘 중 1개 그룹인원과 현재 노드의 인원과 더하면 mid보다 작거나 같다.
이럴때에는 그룹인원이 많은쪽의 간선을 끊는것과 마찬가지 이므로
dp[parent][0] = num[parent] + min(dp[left_node][0], dp[right_node][1])
을 해주고, 그룹은 두 자식노드의 그룹인원을 더해주면 된다.
3. 현재 그룹인원의 mid보다 작거나 같고, 자식노드들의 인원을 더하면 mid보다 크다.
이럴 경우는 간선을 전부 끝는 것이다.
그래서 dp[parent][0] = num[parent]를 해주고,
그룹의 수는 1개를 더 더해주면 된다.
4. 현재 그룹의 인원의 mid보다 크다.
이럴때에는 어떤 방도로도 mid보다 작거나 같은 그룹으로 나눌수 없으므로, 그룹인원의 수를 0으로 초기화 해주고, 그룹의 수를 INF로 해준다.
이 문제는 평소 코에서 나오지 않는 트리dp 문제인데다가, 이분탐색까지 활용해야되서 어려웠던 문제였다.
dp 테이블을 설계하는 것부터, 그룹의수가 k이면서 최소의 그룹인원 수를 어떻게 찾아하는지 생각하기 어려운 문제였다.
이 문제는 카카오 시험이 끝난뒤, 알고리즘 고수분들을 이야기를 통해, 어떻게 풀어야할지 감을 잡았고,
문제가 나오고 실제로 푸는데에도 시간이 오래걸린 문제였다.
이 문제가 다시 나오면 풀수 있다는 확신은 없지만, 한번 경험해봤으니 이러한 문제도 있다라는 것을 배운점에 만족했다.
'알고리즘 > 프로그래머스' 카테고리의 다른 글
[프로그래머스] 92344 파괴되지 않는 건물 2022 KAKAO BLIND RECRUITMENT (0) | 2022.01.15 |
---|---|
[프로그래머스] 92345 사라지는 발판 2022 KAKAO BLIND RECRUITMENT (0) | 2022.01.15 |
[프로그래머스] 81304 미로 탈출 2021 카카오 채용연계형 인턴십 (0) | 2021.07.11 |
[프로그래머스] 81303 표 편집 2021 카카오 채용연계형 인턴십 (0) | 2021.07.11 |
[프로그래머스] 42641 체육복 (0) | 2021.03.14 |