結果
問題 | No.102 トランプを奪え |
ユーザー |
![]() |
提出日時 | 2025-05-14 13:02:02 |
言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
結果 |
AC
|
実行時間 | 139 ms / 5,000 ms |
コード長 | 9,993 bytes |
コンパイル時間 | 740 ms |
コンパイル使用メモリ | 75,840 KB |
実行使用メモリ | 19,552 KB |
最終ジャッジ日時 | 2025-05-14 13:04:05 |
合計ジャッジ時間 | 1,651 ms |
ジャッジサーバーID (参考情報) |
judge1 / judge4 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 3 |
other | AC * 8 |
ソースコード
#include <iostream> #include <vector> #include <numeric> #include <algorithm> #include <cstring> // For std::fill, requires <algorithm>, sometimes <cstring> helps with pointer arithmetic or related types? Included to be safe. // Correct header for std::fill is <algorithm>. <cstring> is for C memory/string functions. using namespace std; // Store initial pile sizes globally int N_initial[4]; // Store total number of cards globally int total_cards; // Constants defining maximum values based on problem constraints const int MAXN = 13; // Maximum number of cards in a single pile const int MAX_TOTAL_CARDS = 52; // Maximum total number of cards (13 * 4) // Memoization table using a multi-dimensional array. // Dimensions correspond to state variables: [n1][n2][n3][n4][h1][player] // Sizes are MAXN+1 for pile sizes (0 to MAXN), MAX_TOTAL_CARDS+1 for Taro's hand size (0 to MAX_TOTAL_CARDS), and 2 for player (0 for Taro, 1 for Jiro). int memo[MAXN + 1][MAXN + 1][MAXN + 1][MAXN + 1][MAX_TOTAL_CARDS + 1][2]; // A special value distinct from any possible score difference, used to indicate that a state has not been computed yet. // The score difference h1 - h2 ranges from -total_cards to +total_cards. Minimum is -52, Maximum is +52. const int UNCOMPUTED = -1e9; // -1 billion is safely outside the possible score range. // Recursive function implementing the minimax algorithm with memoization (Dynamic Programming) // State is defined by (n1, n2, n3, n4, h1, is_taro_turn) // n1..n4: number of cards remaining in each pile // h1: number of cards in Taro's hand // is_taro_turn: boolean indicating whose turn it is (true for Taro, false for Jiro) // Returns the maximum possible final score difference (h1 - h2) achievable from the given state, assuming optimal play. int solve(int n1, int n2, int n3, int n4, int h1, bool is_taro_turn) { // Base case: If all piles are empty, the game ends. if (n1 == 0 && n2 == 0 && n3 == 0 && n4 == 0) { // Calculate Jiro's final hand size (h2). Total cards = h1 + h2. int h2 = total_cards - h1; // Return the final score difference. return h1 - h2; } // Determine the player index for accessing the memoization table: 0 for Taro, 1 for Jiro. int player_idx = is_taro_turn ? 0 : 1; // Use a reference to the memo table entry for efficiency. Avoids copying the state key. int& memo_val = memo[n1][n2][n3][n4][h1][player_idx]; // Check if the result for this state has already been computed and stored in the memo table. if (memo_val != UNCOMPUTED) { return memo_val; // Return the memoized result. } // Calculate Jiro's current hand size (h2). This is needed for the card stealing logic. // Total cards taken so far = total_cards - cards_remaining. // And total cards taken = h1 + h2. int cards_remaining = n1 + n2 + n3 + n4; int cards_taken = total_cards - cards_remaining; int h2 = cards_taken - h1; // Defensive check: In a correct game flow, h1 and h2 should always be non-negative. // Adding max(0, ...) ensures robustness against potential negative values due to bugs elsewhere, though ideally unnecessary. // This check is important because division and ceiling/floor operations assume non-negative inputs based on game logic. h1 = max(0, h1); h2 = max(0, h2); // Variable to store the best score achievable from the current state. int best_score; // Store current pile sizes in an array for easier iteration over possible moves. int current_piles[4] = {n1, n2, n3, n4}; // Logic for Taro's turn (player 0). Taro wants to maximize the final score difference (h1 - h2). if (is_taro_turn) { // Initialize best_score to a very small value. best_score = -1e9; // Iterate through each of the four piles. for (int i = 0; i < 4; ++i) { // Check if the current pile is not empty. if (current_piles[i] > 0) { // Iterate through the possible number of cards to take (1, 2, or 3). for (int k = 1; k <= min(3, current_piles[i]); ++k) { // Calculate the state after Taro takes k cards from pile i. int next_piles[4]; // Array to store the pile sizes for the next state. for(int j=0; j<4; ++j) next_piles[j] = current_piles[j]; // Copy current pile sizes. next_piles[i] -= k; // Update the size of the chosen pile. int next_h1; // Calculate Taro's hand size for the next state. if (next_piles[i] == 0) { // If Taro took the last card from pile i... // Taro gains k cards taken, plus steals ceil(h2/2) cards from Jiro. // ceil(x/2) can be calculated as (x + 1) / 2 using integer division. next_h1 = h1 + k + (h2 + 1) / 2; } else { // If pile i is not empty after taking k cards... // Taro simply gains the k cards he took. next_h1 = h1 + k; } // Recursively call solve for the next state, which will be Jiro's turn (false). // Update best_score with the maximum score found among all possible moves. best_score = max(best_score, solve(next_piles[0], next_piles[1], next_piles[2], next_piles[3], next_h1, false)); } } } } // Logic for Jiro's turn (player 1). Jiro wants to minimize the final score difference (h1 - h2). else { // Initialize best_score to a very large value. best_score = 1e9; // Iterate through each of the four piles. for (int i = 0; i < 4; ++i) { // Check if the current pile is not empty. if (current_piles[i] > 0) { // Iterate through the possible number of cards to take (1, 2, or 3). for (int k = 1; k <= min(3, current_piles[i]); ++k) { // Calculate the state after Jiro takes k cards from pile i. int next_piles[4]; // Array to store the pile sizes for the next state. for(int j=0; j<4; ++j) next_piles[j] = current_piles[j]; // Copy current pile sizes. next_piles[i] -= k; // Update the size of the chosen pile. int next_h1; // Calculate Taro's hand size for the next state. if (next_piles[i] == 0) { // If Jiro took the last card from pile i... // Jiro steals ceil(h1/2) cards from Taro. Taro's hand size becomes floor(h1/2). // floor(x/2) is calculated as x / 2 using integer division. next_h1 = h1 / 2; } else { // If pile i is not empty after taking k cards... // Taro's hand size remains unchanged. next_h1 = h1; } // Jiro's hand size (h2) increases by k cards taken, plus any stolen cards. // This change in h2 is implicitly handled by the calculation within the recursive call based on next_h1 and remaining cards. // Recursively call solve for the next state, which will be Taro's turn (true). // Update best_score with the minimum score found among all possible moves. best_score = min(best_score, solve(next_piles[0], next_piles[1], next_piles[2], next_piles[3], next_h1, true)); } } } } // Store the computed best score for the current state in the memoization table before returning it. return memo_val = best_score; } // Function to initialize the memoization table with the UNCOMPUTED value. // This is done once at the beginning of the program. void initialize_memo() { // Calculate the total number of elements in the multi-dimensional memo array. // Use size_t for potentially large sizes to avoid overflow. size_t total_elements = (size_t)(MAXN + 1) * (MAXN + 1) * (MAXN + 1) * (MAXN + 1) * (MAX_TOTAL_CARDS + 1) * 2; // Get a pointer to the first element of the memo array. This allows treating it as a flat 1D array for initialization. int* p = &memo[0][0][0][0][0][0]; // Use std::fill (from <algorithm>) to efficiently set all elements to the UNCOMPUTED value. std::fill(p, p + total_elements, UNCOMPUTED); } int main() { // Optimize C++ standard input/output streams for faster execution. ios_base::sync_with_stdio(false); cin.tie(NULL); // Read the initial number of cards in each of the four piles from standard input. cin >> N_initial[0] >> N_initial[1] >> N_initial[2] >> N_initial[3]; // Calculate the total number of cards across all piles. total_cards = 0; for(int i=0; i<4; ++i) total_cards += N_initial[i]; // Initialize the memoization table. This ensures that we can detect uncomputed states. initialize_memo(); // Call the recursive function `solve` to determine the outcome of the game starting from the initial state. // Taro starts the game (is_taro_turn = true), and initially has 0 cards (h1 = 0). int final_score_diff = solve(N_initial[0], N_initial[1], N_initial[2], N_initial[3], 0, true); // Determine the winner based on the final score difference (h1 - h2). if (final_score_diff > 0) { // If the score difference is positive, Taro has more cards than Jiro. cout << "Taro" << endl; } else if (final_score_diff < 0) { // If the score difference is negative, Jiro has more cards than Taro. cout << "Jiro" << endl; } else { // If the score difference is zero, they have the same number of cards. cout << "Draw" << endl; } return 0; // Indicate successful program execution. }