結果
問題 |
No.1016 三目並べ
|
ユーザー |
![]() |
提出日時 | 2025-05-14 13:13:33 |
言語 | PyPy3 (7.3.15) |
結果 |
MLE
|
実行時間 | - |
コード長 | 8,862 bytes |
コンパイル時間 | 301 ms |
コンパイル使用メモリ | 82,160 KB |
実行使用メモリ | 683,944 KB |
最終ジャッジ日時 | 2025-05-14 13:14:34 |
合計ジャッジ時間 | 4,572 ms |
ジャッジサーバーID (参考情報) |
judge5 / judge3 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 1 |
other | AC * 6 MLE * 1 -- * 3 |
ソースコード
import sys # Increase recursion depth limit for potentially deep game trees. # This might be necessary if the number of empty cells (k) is large. # The maximum recursion depth could theoretically be k, up to N=1000. # Setting it to 2048 should be sufficient for moderate k values. try: # Set a higher recursion depth limit. Note: this might not work on all platforms # or might be disallowed in some contest environments. sys.setrecursionlimit(2048) except Exception as e: # If setting recursion depth fails, we can optionally print a warning to stderr. # print(f"Warning: Could not set recursion depth limit: {e}", file=sys.stderr) pass # Continue with the default limit if setting fails. # Global dictionary for memoization. Stores results of subproblems (game states). # Keys are tuples (board_tuple, is_O_turn), values are boolean (True if O can win, False otherwise). memo = {} def check_win_condition(board_str): """Checks if the board string contains three consecutive 'o's ('ooo').""" return 'ooo' in board_str # The core recursive function implementing minimax with memoization. def recursive_can_O_win(board_tuple, is_O_turn): """ Determines if player O can force a win from the given board state and turn. This function uses minimax logic assuming optimal play from both sides. Args: board_tuple (tuple): The current state of the board represented as a tuple of characters. Tuples are used because they are hashable and can be dictionary keys. is_O_turn (bool): True if it's player O's turn, False if it's player X's turn. Returns: bool: True if player O can force a win from this state, False otherwise. """ # Create a unique key for the current game state (board configuration + whose turn). state_key = (board_tuple, is_O_turn) # Check if the result for this state is already computed and stored in memo. if state_key in memo: return memo[state_key] # Convert tuple back to string for easier substring checks. board_str = "".join(board_tuple) N = len(board_tuple) # Length of the board # Base Case 1: Check if 'ooo' already exists on the board. # If 'ooo' is present, player O has already won (or secured a win). This is a terminal state. if check_win_condition(board_str): memo[state_key] = True # Store result return True # Find all empty cells represented by '-'. empty_indices = [i for i, char in enumerate(board_tuple) if char == '-'] # Base Case 2: If there are no empty cells left and 'ooo' condition was not met above, # the game ends, and player O has not won. Thus, player X wins. if not empty_indices: memo[state_key] = False # Store result return False # Identify "critical" cells for player O. # A critical cell is an empty cell where placing 'o' would immediately create "ooo". critical_indices = [] for i in empty_indices: is_critical = False # Check if placing 'o' at index i completes 'ooo' pattern: # Pattern 'oo-' becomes 'ooo'? Check cells i-2, i-1. Need S[i-2]=='o' and S[i-1]=='o'. if i >= 2 and board_tuple[i-2] == 'o' and board_tuple[i-1] == 'o': is_critical = True # Pattern 'o-o' becomes 'ooo'? Check cells i-1, i+1. Need S[i-1]=='o' and S[i+1]=='o'. elif i >= 1 and i+1 < N and board_tuple[i-1] == 'o' and board_tuple[i+1] == 'o': is_critical = True # Pattern '-oo' becomes 'ooo'? Check cells i+1, i+2. Need S[i+1]=='o' and S[i+2]=='o'. elif i+2 < N and board_tuple[i+1] == 'o' and board_tuple[i+2] == 'o': is_critical = True if is_critical: critical_indices.append(i) # Minimax logic based on whose turn it is: if is_O_turn: # O's turn (Maximizing player for O's win) # Check if O has an immediate winning move (a critical cell). if critical_indices: # If O can play in a critical cell, O plays it and guarantees 'ooo' on the board. O wins. memo[state_key] = True return True # If no immediate win, O explores all possible moves. # O wants to find at least ONE move that leads to a state from which O can win. can_win = False # Assume O cannot win initially for this path for i in empty_indices: new_board_list = list(board_tuple) # Convert tuple to list for modification new_board_list[i] = 'o' # O places 'o' new_board_tuple = tuple(new_board_list) # Convert back to tuple for recursive call and memo key # Recursively call for the next state (X's turn). If the recursive call returns True, # it means O can force a win after making this move. if recursive_can_O_win(new_board_tuple, False): # Pass turn to X can_win = True break # O found a winning move, no need to explore further moves for O. memo[state_key] = can_win # Store the result for this state return can_win else: # X's turn (Minimizing player for O's win / Maximizing player for X's win) # Check if O has created a "fork" (multiple critical cells). if len(critical_indices) >= 2: # If O has two or more immediate winning moves, X can only block one. # O will play in another critical cell on the next turn and win. memo[state_key] = True return True # Check if O has exactly one immediate winning move. if len(critical_indices) == 1: # X MUST block this single critical cell to prevent O from winning immediately. i = critical_indices[0] new_board_list = list(board_tuple) new_board_list[i] = 'x' # X plays 'x' to block new_board_tuple = tuple(new_board_list) # Check the outcome after X blocks. The result of the recursive call determines the outcome. result = recursive_can_O_win(new_board_tuple, True) # Pass turn back to O memo[state_key] = result return result # If O has no immediate winning moves (no critical cells), X explores all possible moves. # X wants to find at least ONE move that leads to a state where O CANNOT win (X wins). # From O's perspective: O can win from this state only if for ALL moves X makes, O can still win. can_O_win_overall = True # Assume O wins, unless X finds a move to refute this. for i in empty_indices: new_board_list = list(board_tuple) new_board_list[i] = 'x' # X places 'x' new_board_tuple = tuple(new_board_list) # Recursively call for the next state (O's turn). If recursive call returns False, # it means O cannot force a win after X makes this move. X has found a winning path. if not recursive_can_O_win(new_board_tuple, True): # Pass turn to O can_O_win_overall = False break # X found a move that ensures X wins. No need to explore further moves for X. memo[state_key] = can_O_win_overall # Store the result for this state return can_O_win_overall # Main function to handle input/output and test cases. def main(): # Read the number of test cases. T = int(sys.stdin.readline()) results = [] # List to store results for each test case. for _ in range(T): # Read N and the board string S for each test case. line = sys.stdin.readline().split() # N = int(line[0]) # N is board size, also len(S). We mainly use len(S). S = list(line[1]) # Convert string S to list of characters for easier manipulation if needed. # Convert board state list to tuple for use as dictionary key in memoization. board_tuple = tuple(S) # Clear the memoization dictionary for each new test case as they are independent. memo.clear() # Check if the initial board already contains 'ooo'. If so, O wins immediately. if check_win_condition("".join(S)): results.append("O") continue # Move to the next test case. # If no initial win, start the recursive minimax search. # The game always starts with O's turn according to the problem statement. if recursive_can_O_win(board_tuple, True): # Start with O's turn. results.append("O") # O can force a win. else: results.append("X") # O cannot force a win, so X wins. # Print all results, each on a new line. # Using '\n'.join is efficient for printing multiple lines. print('\n'.join(results)) # Standard boilerplate to run the main function. if __name__ == '__main__': main()