結果

問題 No.102 トランプを奪え
ユーザー qwewe
提出日時 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
権限があれば一括ダウンロードができます

ソースコード

diff #

#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.
}
0