結果
| 問題 |
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 |
ソースコード
#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.
}
qwewe