수정입니다

<11052> 카드 구매하기 - DP 본문

백준

<11052> 카드 구매하기 - DP

nongdamgom 2024. 1. 28. 01:02

실버 I

 

 

문제

요즘 민규네 동네에서는 스타트링크에서 만든 PS카드를 모으는 것이 유행이다.
PS카드는 PS(Problem Solving)분야에서 유명한 사람들의 아이디와 얼굴이 적혀있는 카드이다. 각각의 카드에는 등급을 나타내는 색이 칠해져 있고, 다음과 같이 8가지가 있다.

전설카드 / 레드카드 / 오렌지카드 / 퍼플카드 / 블루카드 / 청록카드 / 그린카드 / 그레이카드

카드는 카드팩의 형태로만 구매할 수 있고, 카드팩의 종류는 카드 1개가 포함된 카드팩, 카드 2개가 포함된 카드팩, ... 카드 N개가 포함된 카드팩과 같이 총 N가지가 존재한다.
민규는 카드의 개수가 적은 팩이더라도 가격이 비싸면 높은 등급의 카드가 많이 들어있을 것이라는 미신을 믿고 있다. 따라서, 민규는 돈을 최대한 많이 지불해서 카드 N개 구매하려고 한다. 카드가 i개 포함된 카드팩의 가격은 Pi원이다.

예를 들어, 카드팩이 총 4가지 종류가 있고, P1 = 1, P2 = 5, P3 = 6, P4 = 7인 경우에 민규가 카드 4개를 갖기 위해 지불해야 하는 금액의 최댓값은 10원이다. 2개 들어있는 카드팩을 2번 사면 된다.
P1 = 5, P2 = 2, P3 = 8, P4 = 10인 경우에는 카드가 1개 들어있는 카드팩을 4번 사면 20원이고, 이 경우가 민규가 지불해야 하는 금액의 최댓값이다.
마지막으로, P1 = 3, P2 = 5, P3 = 15, P4 = 16인 경우에는 3개 들어있는 카드팩과 1개 들어있는 카드팩을 구매해 18원을 지불하는 것이 최댓값이다.

카드 팩의 가격이 주어졌을 때, N개의 카드를 구매하기 위해 민규가 지불해야 하는 금액의 최댓값을 구하는 프로그램을 작성하시오. N개보다 많은 개수의 카드를 산 다음, 나머지 카드를 버려서 N개를 만드는 것은 불가능하다. 즉, 구매한 카드팩에 포함되어 있는 카드 개수의 합은 N과 같아야 한다.

 

 

 

입력

첫째 줄에 민규가 구매하려고 하는 카드의 개수 N이 주어진다. (1 ≤ N ≤ 1,000)
둘째 줄에는 Pi가 P1부터 PN까지 순서대로 주어진다. (1 ≤ Pi ≤ 10,000)

 

 

 

출력

첫째 줄에 민규가 카드 N개를 갖기 위해 지불해야 하는 금액의 최댓값을 출력한다.

 

 

 

풀이

import sys

n = int(sys.stdin.readline().rstrip())
p = [0] + list(map(int, sys.stdin.readline().rstrip().split()))
dp = [0] * (n+1)

for i in range(1, n+1):
    dp[i] = p[i]
    for j in range(1, i):
        dp[i] = max(dp[i], dp[i-j] + p[j])


print(dp[n])

 

멍청 이슈로 시간이 좀 오래 걸렸다...

 

어려웠던 점 

  • 어떻게 n개의 연속된 숫자들의 합으로 n이 되는 경우의 수를 따져 줄 것인지
  • 그럴 경우에 dp 테이블을 어떻게 설정해야 하는지

 

TRY 1.

멍청하게도... 전 포스팅에서 dp는 그렇게 푸는게 아니라고 계속 말했건만... 

경우의 수를 어떻게 나눠야 할지... 확통적으로 접근해서 점화식을 세우려고 함.

그래도 나름 발전한게 점화식을 세우려고 도전한 거 ㅋ

1부터 4까지 되는 경우를 쭉 나열하고 그 사이에서 1이 2가 될 때, 2가 3이 될 때 어떤 관계로 넘어가나 찾아봄

 

==> 규칙을 못찾겠음 ㅋㅋ 약간 멘붕

 

 

TRY 2.

위에서 딱 떨어지는 점화식을 못찾아서,

>아, dp 테이블을 2차원으로 만들어서 각 1차원에 각 숫자가 될 경우의 수를 적어야겠다< 고 생각

예를 들어 dp[1][0] 에는 1 * 1 + 1 * 1 , dp[1][1] 에는 2 * 1 

이런 식으로 모든 경우의 수를 다 써내려가면서 어떻게 잘 조합하면(?) 최대값을 찾을 수 있을거라고 생각함

 

==> 구현 이슈인지, 생각을 잘못한건지 모르겠으나 실패

 

 

TRY 3.

위에서 좀 더 구체화 해서 이중 포문 조건을 좀 더 걸고, 이리저리 시도 했으나 실패

구구절절 쓰기 귀찮아서 간략하게 적어보자면, 최종 정답이랑 알고리즘은 유사했으나

구현 이슈로 실패한 듯

==> 여기서 깨달은 점

**dp 풀때는 웬만하면 dp[0]은 그냥 자리 채우기 용으로 냅두고 dp[1]부터 숫자 맞춰서 가자**

왜냐면 이 문제는 0이 들어가면

4 = 1 + 3 , 2 + 2, 1 + 3 이게 안되고

3 = 3 + 0, 2 + 1, 1 + 2 <  이렇게 인덱스로 더하기 처리가 돼서 알고리즘이 맞아도 값이 잘못나옴

 

==> 맞는거 같은데 안돼서 결국 그냥 답 찾아봄

 

 

 

결론적으로는 이러한 알고리즘이다.

for i in range(1, n+1):
    dp[i] = p[i]
    for j in range(1, i):
        dp[i] = max(dp[i], dp[i-j] + p[j])

 

사실 처음에 이걸 보고도 왜 이 구현이 최대값을 구하는걸까...... 하고 생각함

그래서 걍 손으로 써봄

 

 

내가 생각하지 못했던 부분은

나는 n = 4로 주어진다면, dp[4]를 구하기 위해서

dp[1], dp[2], dp[3]을 쩌리 취급하고 나중에 dp[4]에 가서 취사 선택으로 최대값을 구하려고 했다.

 

그런데 dp 문제를 풀면서 느낀 부분은

n = 4로 주어졌어도, dp[1],dp[2],dp[3] 에서부터 최대값을 찾아서 올라와야 한다는 점...

머리로는 알고 있는데 자꾸 뇌가 딴길로 샌다...

 

그러니까

dp[n] 은 p[n] or (n 이 될 수 있는 dp[i]의 최대값 +  합이 n이 되기 위한 p값) 을 for 문 돌려주는 것이다.

 

예를 들어

dp[3] (3장 살 때 최대값) = 

p[3] (애초에 3장짜리 카드팩 사는거) 이거나

dp[2] (2장 살 때 최대값)  + p[1](3장 사야해서 1장짜리 카드팩 더 사는 거) 이거나

dp[1] (1장 살 때 최대값) + p[2](3장 사야해서 2장짜리 카드팩 더 사는 거) 이거나

세가지 경우의 수가 존재한다는 뜻

 

여기서 좀 배워간게 

여태까지 dp값 max, min 구할 때 , 점화식이 다 2가지 경우를 비교하는 거라 여러가지를 어떻게 할지 몰랐는데,

이렇게 dp[i]를 계속 갱신시키면서 for문을 돌리면 여러 경우 max 값 구할 수 있겠구나 깨달음

 

 

 

어렵다...............

dp 문제는 한 번 헤매기 시작하면 끝도 없이 돌아가고, 유레카 하는 순간 개쉬워지는듯...

연습하면 늘겠지 휴

'백준' 카테고리의 다른 글

<11727> 2 x n 타일링2  (1) 2024.01.30
<2011> 암호코드 - DP  (2) 2024.01.30
<9465> 스티커 - DP  (1) 2024.01.26
<2579> 계단 오르기 - DP  (0) 2024.01.25
<1920> 수 찾기 - 이진탐색  (1) 2024.01.25