2023. 9. 6. 13:16ㆍProblem Solving/DP
브루트포스로 풀면 당연히 시간초과가 날 것이다.
최악의 경우 O(2^n) 이고, n의 최댓값은 1000이므로 시간초과를 예상할 수 있다.
브루트포스의 과정을 생각해보면 같은 연산을 하게 되는 부분이 분명히 존재할 것이다.
예를 들어 [1, 2, 3, 4, 5, 6] 과 같이 카드가 있다고 가정해보자.
근우가 6, 5 를 가져가고 명우가 1, 2를 가져가는 경우든
근우가 6, 1 을 가져가고 명우가 5, 2를 가져가는 경우든
남아있는 카드는 [3, 4] 이다.
즉, 이전에 어떤 카드들이 선택되었든 남아있는 카드가 동일하다면 해당 카드들을 대상으로는 근우가 얻을 최대 점수는 항상 같을 것이다.
이를 이용해 dp를 활용하면 시간초과를 피할 수 있을 것이다.
나는 game() 이라는 함수를 정의해 dp배열을 채웠다.
dp[i][j]는 i번째 카드부터 j번째 카드가 남아있을 때 근우가 얻을 수 있는 최대 점수이다.
dp[i][j]가 0이 아니라면 한 번 계산된 경우이므로 추가적인 연산 필요없이 해당 값을 바로 참조하면 된다.
0이라면 한 번 계산해야 하는데
근우의 턴일 경우 dp값은 max(왼쪽 카드 + 왼쪽 카드를 제외한 경우에서의 최대 점수, 오른쪽 카드 + 오른쪽 카드를 제외한 경우에서의 최대 점수) 이다.
명우의 턴일 경우에는 dp값은 min(왼쪽 카드를 제외한 경우에서의 최대 점수, 오른쪽 카드를 제외한 경우에서의 최대 점수) 이다.
명우의 턴일 때가 잘 이해되지 않을 수 있다.
dp값은 다시 말하지만 "근우"가 얻을 수 있는 최대 점수이다.
명우의 턴일 때 명우 또한 최선의 전략으로 카드를 선택한다.
명우의 턴일 때 근우는 명우가 선택한 카드의 점수를 얻을 수 없고, 양 쪽 카드 중 제외한 경우에서의 최대 점수가 작은 쪽의 점수만을 얻을 수 있다.
정답 코드
#include <iostream>
#include <cstring>
using namespace std;
int T, N, card[1000], dp[1000][1000];
int game(int left, int right, int turn){
if (left > right)
return 0;
if (dp[left][right] != 0)
return dp[left][right];
if (turn == 1)
return dp[left][right] = max(card[left] + game(left + 1, right, 0), card[right] + game(left, right - 1, 0));
else
return dp[left][right] = min(game(left + 1, right, 1), game(left, right - 1, 1));
}
int main(){
cin.tie(NULL);
cout.tie(NULL);
ios_base::sync_with_stdio(false);
//input
cin >> T;
while (T--){
cin >> N;
for (int i = 0 ; i < N; i++)
cin >> card[i];
//dp
game(0, N - 1, 1);
//output
cout << dp[0][N - 1] << "\n";
memset(dp, 0, sizeof(dp));
}
}'Problem Solving > DP' 카테고리의 다른 글
| [백준(BOJ)] 돌 게임(9655번)_C++ (0) | 2024.03.13 |
|---|---|
| [백준(BOJ)] 이진 트리(13325번)_C++ (0) | 2023.09.18 |
| [백준(BOJ)] LCS(9251번)_C++ (0) | 2023.04.09 |
| [백준(BOJ)] 경찰차(2618번)_C++ (0) | 2023.03.30 |
| [백준(BOJ)] ACM Craft(1005번)_C++ (0) | 2023.03.23 |