수정입니다
<9465> 스티커 - DP 본문
실버 I
문제
상근이의 여동생 상냥이는 문방구에서 스티커 2n개를 구매했다. 스티커는 그림 (a)와 같이 2행 n열로 배치되어 있다. 상냥이는 스티커를 이용해 책상을 꾸미려고 한다.
상냥이가 구매한 스티커의 품질은 매우 좋지 않다. 스티커 한 장을 떼면, 그 스티커와 변을 공유하는 스티커는 모두 찢어져서 사용할 수 없게 된다. 즉, 뗀 스티커의 왼쪽, 오른쪽, 위, 아래에 있는 스티커는 사용할 수 없게 된다.
모든 스티커를 붙일 수 없게된 상냥이는 각 스티커에 점수를 매기고, 점수의 합이 최대가 되게 스티커를 떼어내려고 한다. 먼저, 그림 (b)와 같이 각 스티커에 점수를 매겼다. 상냥이가 뗄 수 있는 스티커의 점수의 최댓값을 구하는 프로그램을 작성하시오. 즉, 2n개의 스티커 중에서 점수의 합이 최대가 되면서 서로 변을 공유 하지 않는 스티커 집합을 구해야 한다.
위의 그림의 경우에 점수가 50, 50, 100, 60인 스티커를 고르면, 점수는 260이 되고 이 것이 최대 점수이다. 가장 높은 점수를 가지는 두 스티커 (100과 70)은 변을 공유하기 때문에, 동시에 뗄 수 없다.
입력
첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스의 첫째 줄에는 n (1 ≤ n ≤ 100,000)이 주어진다. 다음 두 줄에는 n개의 정수가 주어지며, 각 정수는 그 위치에 해당하는 스티커의 점수이다. 연속하는 두 정수 사이에는 빈 칸이 하나 있다. 점수는 0보다 크거나 같고, 100보다 작거나 같은 정수이다.
출력
각 테스트 케이스 마다, 2n개의 스티커 중에서 두 변을 공유하지 않는 스티커 점수의 최댓값을 출력한다.
풀이
import sys
t = int(sys.stdin.readline().rstrip())
while t > 0:
n = int(sys.stdin.readline().rstrip())
point = [list(map(int, sys.stdin.readline().rstrip().split())) for _ in range(2)]
dp = [[0] * n for _ in range(2)]
""" dp = [[0]*n]*2 이렇게 하면 그냥 1차원 2차원 복사라서
값 넣을 때 똑같은 1차원 리스트 2개 복사 되는 형태가 됨!!!! < 서로 다른 값 못넣게 됨
주의 *** """
if n == 1:
print(max(point[0][0], point[1][0]))
elif n == 2:
print(max(point[0][0]+point[1][1], point[0][1]+point[1][0]))
else:
dp[0][0] = point[0][0]
dp[1][0] = point[1][0]
dp[0][1] = dp[1][0] + point[0][1]
dp[1][1] = dp[0][0] + point[1][1]
for i in range(2,n):
for j in range(2):
dp[j][i] = max(dp[j-1][i-1]+point[j][i], dp[j-1][i-2] + point[j][i])
print(max(dp[0][n-1], dp[1][n-1]))
t -= 1
전날 풀었던 <계단 오르기> 문제에서 dp 푸는 방법을 정리한게 좀 도움이 됐음.
앞에서부터 차근차근히 n = 1일 때는 최대값을 구하는 경로가 어떻게 되지? n = 2 일 때는?
이렇게 생각하니까, n = 1, 2 일 때는 고정 값이라는 것을 깨달음 => 미리 dp 테이블에 넣어놓기
** 이 때 dp 테이블을 1차원으로 할지 2차원으로 할지 고민을 했다.
1차원으로 했을 때?
어차피 위 아래 동시에 뗄 수 있는 경우는 없기 때문에, 둘 중 max 값을 dp 테이블에 넣으면 되지 않을까? 하고 생각함
=> 하지만 max(case 1, case 2) 로 했을 때, 위에 값이 선택될 지, 아래 값이 선택 될 지 알 수 없었고,
n >= 3 이라면 전 열에서 몇 행의 값이 선택 됐는지는 반드시 필요한 정보.
but 이것을 알려면 if로 또 조건을 걸어서 하거나, 혹은 불가능해보였음
그리고 n >= 3부터는 경로를 취사선택 해야하기 때문에,
각 column 마다 거기까지 도달했을 때의 최대값이 필요하다는 사실을 인지
==> 2차원으로 밀고 나감
n = 3 이상부터 max 값을 선택하는 경로가 생기는데, 어떤 경로가 있는지 먼저 파악함
이런 문제 풀 때, 특히나 백준에서는 예시가 힌트가 되는 경우가 왕왕 있다. 잘 살펴보기.
나는 예시에서 100 => 10 => 40 이렇게 가지 않고 100 => 60 을 선택하고, 이게 최대값이라는 사실에 집중함
머리로 하려니까 복잡해서 그림 그렸다.. 차라리 이렇게 계속 손으로 그리고 정리하려는 습관 들이는게 나을듯
아무튼 세번째 이상의 열에서는 그림처럼 값이 두군데에서 올 수 있음.
그 두 경로 중 최대값을 찾아서 dp 테이블에 저장하다보면, 해결 할 수 있을 것 같다는 생각을 함.
dp[0][0] = point[0][0]
dp[1][0] = point[1][0]
dp[0][1] = dp[1][0] + point[0][1]
dp[1][1] = dp[0][0] + point[1][1]
for i in range(2,n):
for j in range(2):
dp[j][i] = max(dp[j-1][i-1]+point[j][i], dp[j-1][i-2] + point[j][i])
print(max(dp[0][n-1], dp[1][n-1]))
핵심이 되는 부분은 dp[j][i] = max(dp[j-1][i-1]+point[j][i], dp[j-1][i-2] + point[j][i]) 이 부분
위의 그림의 두 경로를 그대로 점화식으로 만들었다고 생각하면 되는데, 이게 생각보다 한번에 딱 만들기가 어렵..
- dp[j][i] : 현재 내가 구하려는 dp 테이블 컬럼 값
- dp[j-1] : 내가 구하려는 컬럼의 """ 다른 행 """ 에 있는 컬럼값
- ==> 같은 행의 경우도 있지 않나? 의심 해봤는데 없었음 (2행이라서 그런듯)
- ==> 왜냐면 point 중 negative가 있지 않는 이상 같은 행에서 오는건 w 모양으로 오는게 무조건 더 큰값임
- dp[j-1][i-1]+point[j][i] : 바로 전의 열에서 올라온 값과
- dp[j-1][i-2] + point[j][i] : 전전 열에서 올라온 값 중
- 더 큰 값을 취하겠다는 뜻
마지막으로 print(max(dp[0][n-1], dp[1][n-1])) 이 부분이 아직 명확히 풀리지는 않는다.
아 물론 내가 쓴거긴 한데,
처음에는 그냥 dp 테이블 전체에서 max 값을 뽑아서 출력하려고 했음
=> 어떤 문법 상의 이슈? 로 그게 안되더라고.. 왜그러는지 파악을 못함
아무튼 그래서 더 생각을 해보니까
어차피 최대값을 구하는 것이라면, 하나라도 더 더하는 것이 이득일테고,
어떤 경로로 오는 지는 알 수 없지만, 어쨌든 1행이나 2행 둘 중에 하나로 온다면,
마지막 열의 1행 2행 둘 중에 하나는 무조건 더해지는 것이 최대값이 되겠다. 라고 생각함.
==> 그래서 그냥 마지막 열 의 1행 2행 중 최대값을 출력하는 것으로 마무리 함
+) 바보같은 실수 공유
마지막에 출력문을 else 문 밖에다가 써서 99% 까지 갔다가 틀렸습니다 뜸... ㅋ
경우를 나누어주었으면, 각 경우마다 출력을 할 수 있게 설계를 잘 해야함... 다 풀었다고 정줄 놓지 말고..!!
'백준' 카테고리의 다른 글
<2011> 암호코드 - DP (2) | 2024.01.30 |
---|---|
<11052> 카드 구매하기 - DP (1) | 2024.01.28 |
<2579> 계단 오르기 - DP (0) | 2024.01.25 |
<1920> 수 찾기 - 이진탐색 (1) | 2024.01.25 |
<1463> 1로 만들기 - DP (1) | 2024.01.23 |