2024. 1. 31. 10:07ㆍ알고리즘
문제
개미 전사는 부족한 식량을 충당하고자 메뚜기 마을의 식량창고를 몰래 공격하려고 한다. 메뚜기 마을에는 여러 개의 식량창고가 있는데 식량창고는 일직선으로 이어져 있다. 각 식량창고에는 정해진 수의 식량을 저장하고 있으며 개미 전사는 식량창고를 선택적으로 약탈하여 식량을 빼앗을 예정이다. 이때 메뚜기 정찰병들은 일직선상에 존재하는 식량창고 중에서 서로 인접한 식량창고가 공격받으면 바로 알아챌 수 있다. 따라서 개미 전사가 정찰병에게 들키지 않고 식량창고를 약탈하기 위해서는 최소한 한 칸 이상 떨어진 식량창고를 약탈해야 한다. 예를 들어 식량창고 4개가 다음과 같이 존재한다고 가정하자.
{1, 3, 1, 5}
이때 개미 전사는 두 번째 식량창고와 네 번째 식량창고를 선택했을 때 최댓값인 총 8개의 식량을 빼앗을 수 있다. 개미 전사는 식량창고가 이렇게 일직선상일 때 최대한 많은 식량을 얻기를 원한다.
개미 전사를 위해 식량창고 N개에 대한 정보가 주어졌을 때 얻을 수 있는 식량의 최댓값을 구하는 프로그램을 작성하시오.
입력 조건
- 첫째 줄에 식량창고의 개수 N이 주어진다. (3 <= N <= 100)
- 둘째 줄에 공백으로 구분되어 각 식량창고에 저장된 식량의 개수 K가 주어진다. (0 <= K <= 1,000)
출력 조건
- 첫째 줄에 개미 전사가 얻을 수 있는 식량의 최댓값을 출력하시오.
입력 예시
4
1 3 1 5
출력 예시
8
이 문제 또한 그림으로 도식화한 뒤에 생각하면 된다.
나는 생각을 잘못해서 현재 식량창고와 한 칸 이상 떨어져 있으면 다 더할 수 있다고 생각했다.
1 3 1 5
예를 들어 1, 3, 1, 5가 있을 때 현재 식량창고가 1이면, 1과 5까지 더해서 7이 되는 것이다.
하지만 문제의 의도는 각각의 식량창고에 대해 한 칸 이상 떨어져 있어야 한다.
1 3 1 5
이렇게 되어 있을 때 가능한 조합은 {1, 1}, {1, 5}, {3, 5} 이렇게 세 개이다.
이 문제의 점화식은 어떻게 세울가?
왼쪽부터 차례대로 식량창고를 털지 안 털지를 결정하는 경우와 특정한 j번째 식량창고에 대해서 털지 안 털지의 여부를 결정할 때 단 2가지 경우에 대해서만 확인하면 된다.
1. (i - 1)번째 식량창고를 털기로 결정한 경우 현재의 식량창고를 털 수 없다.
1 3 1 5 7
^
여기를 털면
현재 식량창고가 7이라고 가정할 때 5를 털면 현재 식량창고인 7을 털 수 없다.
2. (i - 2)번째 식량창고를 털기로 결정한 경우 현재의 식량창고를 털 수 있다.
1 3 1 5 7
^
여기를 털면
현재 식량창고가 7이라고 가정할 때 1을 털면 현재 식량창고인 7을 털 수 있다.
여기서 알아둘 점은 i번째 식량창고에 대한 최적의 해를 구할 때 왼쪽부터 (i - 3)번째 이하의 식량창고에 대한 최적의 해에 대해서는 고려할 필요가 없다는 점이다.
예를 들어 d[i - 3]는 d[i - 1]과 d[i - 2]를 구하는 과정에서 이미 계산되었기(고려되었기) 때문에, d[i]의 값을 구할 때는 d[i - 1]과 d[i - 2]만 고려하면 된다. 따라서 i번째 식량창고에 있는 식량의 양이 k(i)라고 했을 때 점화식은 다음과 같다.
우선 코드를 보며 시뮬레이션을 해보겠다.
코드
import java.util.*;
public class Main {
// 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
public static int[] d = new int[100];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 정수 N을 입력받기
int n = sc.nextInt();
// 모든 식량 정보 입력받기
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
// 다이나믹 프로그래밍(Dynamic Programming) 진행(보텀업)
d[0] = arr[0];
d[1] = Math.max(arr[0], arr[1]);
for (int i = 2; i < n; i++) {
d[i] = Math.max(d[i - 1], d[i - 2] + arr[i]);
}
// 계산된 결과 출력
System.out.println(d[n - 1]);
}
}
우선 앞서 계산된 결과를 저장하기 위한 DP 테이블을 초기화한다.
그리고 정수 N과 모든 식량 정보를 입력받는다.
이후에는 d[0]에는 arr[0], d[1]에는 arr[0]과 arr[1] 중 더 큰 값을 넣어주고 다이나믹 프로그래밍을 진행해준다.
N은 5이고, 입력값이 1 3 6 4 4라고 가정하고 문제를 풀어보겠다.
1. d[0]에는 1, d[1]에는 3이 들어간다. 그리고 2부터 진행해보겠다.
d[0] = 1;
d[1] = 3;
d[2] = Math.max(d[1], d[0] + arr[2]);
d[2] = Math.max(3, 1+6) = 7
2. 그리고 3을 진행해본다.
d 배열 = {1, 3, 7}
d[3] = Math.max(d[2], d[1] + arr[3]);
d[3] = Math.max(7, 3+4);
d[3] = 7
3. 4를 넣어본다.
d 배열 = {1, 3, 7, 7}
d[4] = Math.max(d[3], d[2] + arr[4]);
d[4] = Math.max(7, 7+4);
d[4] = 11
따라서 배열은 {1, 3, 7, 7, 11}이 되고, n-1에 해당하는 것은 11이다.
선택된 식량창고의 인덱스는 0, 2, 4로 아래와 같다.
1 3 6 4 4
이쯤에서 왜 d[i-3]을 고려할 필요가 없는지 살펴보겠다.
만약 i가 4일 경우 d[1]은 이전에 고려가 되었다.
d[2] = Math.max(d[1], d[0] + arr[2]);
d[3] = Math.max(d[2], d[1] + arr[3]);
d[4] = Math.max(d[3], d[2] + arr[4]);
d[4] = Math.max(Math.max(Math.max(d[1], d[0] + arr[2]), d[1] + arr[3]), d[2] + arr[4)
d[4]는 d[1] 즉 arr[1](3, 인덱스 1)과 arr[0] + arr[2] 중 큰 값이 선택된 것이다.
그리고 d[2]는 arr[1](3, 인덱스 1)과 arr[0] + arr[2] 중 큰 값과 d[1] + arr[3]을 비교하여 더 큰 값이 선택된 것이다.
따라서 d[4]를 구할 때 d[1]을 고려할 필요는 없다.
'알고리즘' 카테고리의 다른 글
[이코테 with Java] 효율적인 화폐 구성 (2) | 2024.02.01 |
---|---|
[이코테 with Java] 바닥 공사 (0) | 2024.01.31 |
[이코테 with Java] 떡볶이 떡 만들기 (0) | 2024.01.29 |
[이코테 with Java] 부품 찾기 (1) | 2024.01.29 |
[이코테 with Java] 두 배열의 원소 교체 (0) | 2024.01.26 |