문제
근우와 명우는 재미있는 카드 게임을 하고 있다. N개의 카드가 일렬로 놓여 있다. 각 카드에는 점수가 적혀있다. 근우부터 시작하여 번갈아가면서 턴이 진행되는데 한 턴에는 가장 왼쪽에 있는 카드나 가장 오른쪽에 있는 카드를 가져갈 수 있다. 카드가 더 이상 남아있지 않을 때까지 턴은 반복된다. 게임의 점수는 자신이 가져간 카드에 적힌 수의 합이다.
근우와 명우는 서로 자신의 점수를 가장 높이기 위해 최선의 전략으로 게임에 임한다. 놓여있는 카드의 개수 N과 카드가 놓여있는 상태가 주어졌을 때 근우가 얻는 점수를 구하는 프로그램을 작성하시오.
예를 들어 카드가 [4, 3, 1, 2]로 놓여있다고 하자. 근우는 처음에 4가 적힌 카드를 가져가고, 명우는 3이 적힌 카드를 가져간다. 그리고 근우는 2가 적힌 카드를 가져가고, 명우는 마지막으로 1이 적힌 카드를 가져간다. 이때 근우와 명우는 최선의 전략으로 임했으며, 근우가 얻는 점수는 6이다.
입력
입력의 첫 줄에는 테스트케이스의 수 T(1 <= T <= 50)가 주어진다.
각 테스트케이스 마다 첫 줄에는 카드의 개수 N(1 <= N <= 1,000)이 주어진다. 두 번째 줄에는 N개의 자연수가 공백으로 구분되어 주어지는데, i번째로 주어지는 수는 왼쪽에서 i번째에 놓인 카드에 적힌 수를 의미한다. 카드에 적혀있는 수는 1이상 10,000이하다.
출력
각 테스트케이스마다 근우와 명우가 최선의 전략으로 임할 때 근우가 얻게되는 점수를 줄로 구분하여 출력한다.
풀이
양 끝에서 하나를 가져갈 수 있기 때문에 구간 DP로 생각하면 자연스럽다. 현재 남아 있는 카드 구간이 [left, right]일 때, 이번 차례가 누구인지에 따라 선택의 의미가 달라진다.
코드에서는 solve(cnt, left, right)를 사용한다. cnt가 짝수면 근우 차례이므로 왼쪽이나 오른쪽 카드 중 더 큰 결과를 만드는 쪽을 고른다. 반대로 홀수면 명우 차례이므로 근우 입장에서는 더 작은 결과가 남도록 명우가 움직인다고 보면 된다.
즉 근우 차례에는 max, 명우 차례에는 min을 취하는 미니맥스 형태의 DP다. 이렇게 하면 별도의 점수 차이 DP를 두지 않아도 근우가 최종적으로 얻는 점수를 직접 계산할 수 있다.
코드
import java.io.*;
import java.util.*;
public class Main {
private static int N;
private static int[] arr;
private static int[][] dp;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
int T = Integer.parseInt(br.readLine());
while (T-- > 0) {
N = Integer.parseInt(br.readLine());
arr = new int[N];
dp = new int[N][N];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i < N; i++)
arr[i] = Integer.parseInt(st.nextToken());
solve(0, 0, N - 1);
sb.append(dp[0][N - 1]).append('\n');
}
System.out.println(sb);
}
private static int solve(int cnt, int left, int right) {
if (cnt == N) return 0;
if (dp[left][right] != 0) return dp[left][right];
if (cnt % 2 == 0)
return dp[left][right] = Math.max(arr[left] + solve(cnt + 1, left + 1, right),
arr[right] + solve(cnt + 1, left, right - 1));
else
return dp[left][right] = Math.min(solve(cnt + 1, left + 1, right),
solve(cnt + 1, left, right - 1));
}
}복잡도
- 시간 복잡도:
- 공간 복잡도:
마무리
카드를 고르는 게임처럼 보이지만, 실제 핵심은 "지금 누구 차례인가"에 따라 max와 min이 번갈아 나온다는 점이다. 구간 DP와 미니맥스 감각을 합치면 깔끔하게 정리된다.
