강의로 돌아가기
전현서

매출 하락 최소화 문제 해설.

난이도가 상당합니다.
해당 해설은, 카카오테크 공식홈페이지를 기반으로 작성된 저만의 주관적인 해설입니다.
해당 내용에 논리적 오류나 오탈자가 발생함에 많은 이해부탁드립니당. :)

아마 해설까지 찾아보신 분들은 해당 문제가,
DFS와 DP를 사용해서 풀어야 한다. 이런 것 쯤은 알고계실겁니다.
즉, 자식노드부터 경우의 수를 계산하여, 점점 부모노드로 확장시켜 가능한 경우의 수를 계산합니다.
계산 결과는 DP에 계속 저장하여, DP의 어느 부분에 결괏값이 저장되게 됩니다.

아마 레벨4에 도전하시면 여러분이라면, DFS의 접근 순서 정도는 기본적으로 알고있으리라 예상됩니다.
DFS에 대한 접근순서가 헷갈린다면, 친절하신 구글 검색 창에 물어보면 많은 정보를 알려주실겁니다.

우리는 DP를 다음과 같이 정의할겁니다.

DP[현재 정점][참석 여부]

현재 정점에 해당되는 배열의 길이는 len(sales) 입니다. 즉, 전체 사원의 수를 의미합니다.
참석 여부에 해당되는 배열의 길이는 2 입니다. 입력데이터에 상관없이 항상 2의 크기를 갖습니다.
만약 전체 사원수가 4명 이라고 하면, DP는

DP = [[0, 0], [0, 0], [0, 0], [0, 0]]

과 같이 초기화 해야함을 알 수 있습니다.
참석여부의 0번 인덱스는 현재 정점이 참석하지 않은 경우 중 가장 적은 매출액을 의미하고,
참석여부의 1번 인덱스는 현재 정점이 참석한 경우 중 가장 적인 매출액을 의미합니다.
우리는 문제에서 사원들을 각 팀에서 한 명씩 워크샵을 보냈을 경우, 워크샵에 고고씽 한 사람들의 매출액의 합이
모든 경우 중에서 가장 최소가 되는 값을 알고 싶어합니다.

해당 알고리즘을 왜 DFS로 풀어야 하는지, 차근차근 알아보도록 하겠습니다.

해당 문제를 풀기 위해서는 가장 최하단에 있는 리프단계의 정점들을 먼저 계산해주어야 합니다.
왜냐하면, 최하단 단계의 DP값은 그냥 눈으로만 봐도 계산이 가능하기 때문입니다.

DP[최하단 단계 정점][1] = sales[최하단 단계 정점]
DP[최하단 단계 정점][0] = 0

리프단계의 정점들의 DP값은 위와 같이 정해집니다. 이유를 알아봅시다.
예를 들어 최하단 리프단계의 정점이 3번 사원이라고 해봅시다.
그리고 sales[3]은 15 라고 해봅시다. 즉, 3번사원의 매출액은 15원입니다.
그렇게 됬을 경우, 현재 정점 기준으로 봤을 때 자기 자신이 참석한 경우인 DP[3][1] 은 자기자신의 매출액이 됩니다.
DP[3][1] = sales[3] = 15
라는 것을 알 수 있습니다. 만약에 자기 자신이 참석하지 않았다면, 결과가 어떻게 될까요?
DP[3][0] = 0
위와 같이 0이됩니다. 만약 자기자신이 워크샵에 참여하지 않았다면,
자신이 팀장인 팀에서 가장 적은 매출액을 내고 있는 팀원이 참여해야 합니다.
하지만, 3번은 최하단 정점이므로, 팀에 속한 팀원이지, 어떤 팀의 팀장을 맡고있지 않습니다.
그렇게 팀원들이 존재하지 않으므로, 자기 자신이 워크샵에 가지 않을 경우는 매출액이 0원이 되는 것을 알 수 있습니다.
이런 식으로 리프 노드부터 각 정점별로 DP값을 계산하여 참여한 경우, 참여하지 않은 두 가지의 경우로 나누면 아래와 같이
일반화가 가능합니다.

경우 1) 자기 자신이 워크샵에 참석한 경우.

  • 자기 자신의 사원번호를 x라 했을 경우, DP[x][1] 의 값을 구하는 문제가 됩니다. 정점 x가 참여한 경우를 의미합니다.
  • 해당 경우에는 자신의 팀의 팀원들의 맡고 있는 총 매출액이 존재합니다. 물론 팀원들이 존재하지 않을 경우의 매출액은 0원입니다.
  • 팀원 각각이 맡고 있는 매출액의 경우의 수 팀원이 참여한 경우 참여하지 않은 경우 중 가장 적은 매출액을 맡고 있을 경우를 팀원별로 전부 더해줍니다.
  • 나온 합산 값에 자기 자신의 워크샵에 참여하므로 자기 자신의 매출액을 더한 값을 자신이 맡게 됩니다.
  • 해당 문제는 팀장이 팀원들의 매출액의 합산을 넘겨받아 1번 정점까지 넘기는 것이 목표입니다.
  • 그렇기에 최종 정답은 min(DP[1][0], DP[1][1]) 이 됩니다. 1번 정점이 자식 정점들의 매출액을 모두 넘겨받아, 1번 정점이 참여한 경우, 1번 정점이 참여하지 않은 경우 2가지 중 값이 적은 것이 최종 답안이 됨을 알 수 있습니다.

DP[x][1] = sales[x] + sum(min(DP[k][0], DP[k][1])) //자식 정점을 k라고 둘 경우, k를 전부 순회하여 최솟값을 합산함.

경우 2) 자기 자신이 워크샵에 참여하지 않았지만, 최솟값을 더할 시에 팀원 중에 참석한 인원이 한 명이라도 있는 경우.

  • 해당 경우는 팀장이 워크샵을 땡땡이를 치는 경우입니다.
  • DP[정점][0] 구하는 문제입니다. 팀장이 참석하지 않은 경우에 해당됩니다. 팀장이 참석하지 않았으면, 경우는 2가지로 나누어지게 됩니다.
  • 각각의 팀원들이 맡고 있는 매출액의 총합 중에서 가장 작은 값을 더해서 자신이 맡아야 합니다.
  • 그런데 팀원 중에서 팀원 자신이 참여한 경우가 최소 매출액일 가능성도 존재합니다. 즉, DP[k][1] >= DP[k][0]이 존재한다는 말이됩니다.
  • 그런 경우에는 팀장이 워크샵에 참석하지 않아도 팀원들의 매출액의 합산만 더해도 논리에 맞다는 것을 알 수 있습니다.

DP[x][0] = sum(min(DP[k][0], DP[k][1])) //자식 정점을 k라고 둘 경우, k를 전부 순회함. (단, DP[k][1] >= DP[k][0]이 되는 정점이 존재할 경우만)

경우 3) 자기 자신이 워크샵에 참여하지 않았는데도, 팀원들의 매출액의 최솟값을 계산 할 때, 팀원 중에서 한 명도 참석하지 않은 경우.

  • DP[정점][0]를 구하는 두 번째 경우입니다.
  • 자기 자신이 참여하지 않았다는 전제로 계산해야 하므로, 자식 정점중에서는 반드시 한명이라도 참석해야 조건에 부합합니다.
  • 하지만, 각 팀원들의 최소 매출액을 계산하면은, 전부다 DP[k][0]이 최소가 되는 값밖에 존재하지 않아. 팀원들이 참석하는 경우를 만들어 내야합니다.
  • 팀원 중 한 명은 반드시 참석해야 하므로, DP[k][1] - DP[k][0] 이 최소가 되는 팀원의 매출액을 추가로 더해주어야합니다.
  • 팀장이 참석하지 않으면, 팀장이 맡아야 매출액은 팀원들의 각 매출액의 최소값의 총합이 됩니다. 하지만, 전부다 DP[k][0]의 값에 해당하므로,
  • 강제적으로 누군가는 DP[k][0] 값을 빼고, DP[k][1]을 더해주어야 합니다. 그래야 팀장이 참여하지 않을 수 있습니다.
  • 그렇기에 -DP[k][0] + DP[k][1] 이 최소가 되는 값을 더해주어야 팀장이 맡을 매출액의 합산이 줄어듦을 알 수 있습니다.
  • 덧셈은 교환법칙이 성립하므로 최종적으로는 각 팀원 k에 대해, min(DP[k][1] - DP[k][0]) 값을 추가적으로 기존의 최소합산값에 추가로 더해주면 됩니다.
  • 해당 경우는, 팀원들이 등장하지 않은 경우가 항상 최솟값이므로 절대로 DP[k][1] - DP[k][0] 가 음수가 되지 않음을 보장할 수 있습니다.

DP[x][0] = sum(min(DP[k][0], DP[k][1])) + min(DP[k][1] - DP[k][0]) //팀원들의 정점 k에대해 전부 순회함, 단, DP[k][0] > DP[k][0]일 경우만)

위와 같이 세 가지의 경우를 가지고 dfs로 순회하면, 우리가 원하는 값인 min(DP[1][0], DP[1][1])을 구해낼 수 있습니다.
그럼 간단하게 DFS의 구조를 간단하게 확인해보겠습니다.


def dfs(v):
    변수 선언

    for문으로 자식 정점 순회
        dfs(k) #자식 정점을 dfs를 보냄, 그래야 자식 정점의 매출액 경우의 수를 가져올 수 있음.

        합산변수 = min(DP[k][1], DP[k][0]) #k를 순회하며 자식 정점의 매출액 중 가장 적은 값을 더함.

    경우1) 수행

    if 조건문:
       경우2) 수행
    else:
       경우3 수행

위와 같이 dfs를 구성할 수 있습니다.
for문으로 자식정점을 탐색하려고 할 때 반드시 dfs를 재귀호출하여 자식 정점에 대한 DP값을 먼저 산출하도록합니다.
그래야 부모 정점에서 자식정점의 DP값의 최솟값을 확인하고 합산을 할 수 있습니다.
해당 원리가 이해가 가지 않는다면, 반드시 DFS에 대한 구조를 숙지하시길 바랍니다.

해설은 여기까지이며, 도움이 되었으면 좋겠습니다.
20000하겠습니다.

  • Bascat

    이 분 설명은 항상 친절하고 이해하기 쉬운 것 같네요

    Bascat―2023.09.10 08:51
  • 전현서

    굳굳!!

    전현서―2023.11.06 20:27
  • baekwoo11@gmail.com

    amen

    baekwoo11@gmail.com―2024.11.16 12:33
  • 고라니즘

    정말 감사합니다..

    고라니즘―2024.12.19 02:49
  • ass3027

    덕분에 이해했습니다 감사합니다..

    ass3027―2025.01.17 15:15
5 개의 답변
김준서

기가 막힌 설명이네요 선생님 이해하는데 도움이 많이 됐습니다 감사합니다!

  • 전현서

    미숙한 저의 실력이지만, 도움이 되셨다니 정말 기쁩니다!

    전현서―2023.11.06 20:26
laksj113@gmail.com

선생님... 감사합니다.

  • 전현서

    화이팅!..

    전현서―2023.11.06 20:26
WonJungYeun

와우 진짜 까다로운 문제네요... 자세한 해설 감사합니다

  • 전현서

    맞습니다. 너무 까다로운 문제입니답. 감사해주셔서 감사합니다 :)

    전현서―2023.11.06 20:26
인길환

'워크샵에 고고씽' 단어선택 좋았다.
딱딱할 수 있는 설명 분위기에 유쾌함을 불어 넣었다.

  • 전현서

    약간? 단어 선택이 딱딱하면 글쓰기가 힘들어지더라구요 데헷 >_<

    전현서―2023.11.06 20:27
yw7148

와 설명 너무 깔끔하네요. 감사합니다.
근데 경우 2) 에서 DP[k][1] >= DP[k][0]가 존재하는 경우라고 하셨는데 DP[k][1] <= DP[k][0] 아닌가요...? 이래야 DP[k][1]이 min에서 뽑히게 되고, 한명이라도 가는 상황이 나올거 같습니다.

너무 친절한 설명 감사합니다.

답변 쓰기
이 입력폼은 마크다운 문법을 지원합니다.