문제
상근이의 여동생 상냥이는 문방구에서 스티커 2n개를 구매했다. 스티커는 그림 (a)와 같이 2행 n열로 배치되어 있다. 상냥이는 스티커를 이용해 책상을 꾸미려고 한다.
상냥이가 구매한 스티커의 품질은 매우 좋지 않다. 스티커 한 장을 떼면, 그 스티커와 변을 공유하는 스티커는 모두 찢어져서 사용할 수 없게 된다. 즉, 뗀 스티커의 왼쪽, 오른쪽, 위, 아래에 있는 스티커는 사용할 수 없게 된다.
모든 스티커를 붙일 수 없게된 상냥이는 각 스티커에 점수를 매기고, 점수의 합이 최대가 되게 스티커를 떼어내려고 한다. 먼저, 그림 (b)와 같이 각 스티커에 점수를 매겼다. 상냥이가 뗄 수 있는 스티커의 점수의 최댓값을 구하는 프로그램을 작성하시오. 즉, 2n개의 스티커 중에서 점수의 합이 최대가 되면서 서로 변을 공유 하지 않는 스티커 집합을 구해야 한다.
위의 그림의 경우에 점수가 50, 50, 100, 60인 스티커를 고르면, 점수는 260이 되고 이 것이 최대 점수이다. 가장 높은 점수를 가지는 두 스티커 (100과 70)은 변을 공유하기 때문에, 동시에 뗄 수 없다.
입력
첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스의 첫째 줄에는 n (1 ≤ n ≤ 100,000)이 주어진다. 다음 두 줄에는 n개의 정수가 주어지며, 각 정수는 그 위치에 해당하는 스티커의 점수이다. 연속하는 두 정수 사이에는 빈 칸이 하나 있다. 점수는 0보다 크거나 같고, 100보다 작거나 같은 정수이다.
출력
각 테스트 케이스 마다, 2n개의 스티커 중에서 두 변을 공유하지 않는 스티커 점수의 최댓값을 출력한다.
입출력 예시
입력
2
5
50 10 100 20 40
30 50 70 10 60
7
10 30 10 50 100 20 40
20 40 30 50 60 20 80
출력
260
290
풀이
'use strict';
const fs = require('fs');
// 백준 제출 할 때 주석 제거
// const readFileSyncAddress = '/dev/stdin';
// VSC 테스트 할 때 주석 제거
const readFileSyncAddress = 'input.txt';
const input = fs.readFileSync(readFileSyncAddress).toString().trim().split('\n');
const T = Number(input.shift());
function solution(N, original) {
const result = [];
const dp = Array.from({ length: 2 }, () => Array(N).fill(0));
dp[0][0] = original[0][0];
dp[1][0] = original[1][0];
for (let j = 1; j < N; j++) {
for (let i = 0; i < 2; i++) {
dp[i][j] = Math.max(dp[i + (-1) ** i][j - 1] + original[i][j], dp[i][j]);
if (j + 1 < N)
dp[i][j + 1] = Math.max(dp[i + (-1) ** i][j - 1] + original[i][j + 1], dp[i][j + 1]);
}
}
dp.forEach(l => result.push(Math.max(...l)));
return Math.max(...result);
}
for (let i = 0; i < T; i++) {
const N = Number(input.shift());
const original = [];
original.push(input.shift().split(' ').map(Number));
original.push(input.shift().split(' ').map(Number));
console.log(solution(N, original));
}
아래 그림처럼, 첫 번째 줄의 50에서 시작했을 때 선택 가능한 다음 위치는 빨간색 원(50) 또는 파란색 원(70) 두 곳입니다.
네 번째 칸의 10을 선택했다고 가정해보면, 최댓값이 될 수 있는 경로는 결국 50 → 100 → 10처럼 초기에 빨간 원(50)이나 파란 원(70)을 선택했을 때와 동일한 흐름으로 이어지게 됩니다.
즉, 파란색 원 다음 줄부터는 같은 흐름 안에 포함되기 때문에, 우리는 처음에 갈라지는 두 가지 분기점만 고려하면 충분합니다.
아래 그림처럼, 우리는 특정 빨간색 칸의 최댓값을 구하고 싶습니다.
이 칸에 도달하기 위해 올 수 있는 경로는 두 개의 빨간 원을 더하는 경우가 있습니다.
그런데, 이미 파란색 원에서 먼저 계산이 진행되어 빨간색 칸에 이미 어떤 값이 저장되어 있는 상황을 가정해봅시다.
그렇다면 우리는 지금 새로 계산한 값과, 기존에 저장된 값을 비교해서 더 큰 값을 선택하면 됩니다.
즉, 현재 칸 값 = max(기존 값, 새로운 경로를 통한 값) 이 식을 통해 항상 최댓값을 유지할 수 있습니다!
결국 파란색 칸과 빨간색 칸일때의 두가지 경우의 수만 고려해주며 dp배열을 완성시키면 되는 것입니다.
dp[1][j] = Math.max(dp[0][j - 1] + original[1][j], dp[1][j]);
dp[1][j + 1] = Math.max(dp[0][j - 1] + original[1][j + 1], dp[1][j + 1]);
하지만 위의 점화식은 50에서 시작. 즉, dp[1]밖에 구하지 못합니다.
따라서, 30에서 시작하였을때도 고려해주어야합니다.
dp[0][j] = Math.max(dp[1][j - 1] + original[0][j], dp[0][j]);
dp[0][j + 1] = Math.max(dp[1][j - 1] + original[0][j + 1], dp[0][j + 1]);
dp[0]을 구하기 위한 점화식은 위처럼 나옵니다.
최종 점화식
for (let j = 1; j < N; j++) {
for (let i = 0; i < 2; i++) {
dp[i][j] = Math.max(dp[i + (-1) ** i][j - 1] + original[i][j], dp[i][j]);
if (j + 1 < N)
dp[i][j + 1] = Math.max(dp[i + (-1) ** i][j - 1] + original[i][j + 1], dp[i][j + 1]);
}
}
'알고리즘' 카테고리의 다른 글
[ 백준 ] 21314 민겸 수 - JavaScript (0) | 2025.04.20 |
---|---|
[ 백준 ] 1325 효율적인 해킹 - JavaScript (0) | 2025.04.09 |
[ 백준 ] 2512 예산 - JavaScript (0) | 2025.04.04 |
[ 백준 ] 2961 도영이가 만든 맛있는 음식 - JavaScript (0) | 2025.04.03 |
[ 백준 ] 1260 DFS와 BFS - JavaScript (0) | 2025.03.31 |