結果

問題 No.2069 み世界数式
ユーザー qwewe
提出日時 2025-05-14 13:05:01
言語 PyPy3
(7.3.15)
結果
WA  
実行時間 -
コード長 14,479 bytes
コンパイル時間 177 ms
コンパイル使用メモリ 82,104 KB
実行使用メモリ 79,724 KB
最終ジャッジ日時 2025-05-14 13:06:52
合計ジャッジ時間 6,592 ms
ジャッジサーバーID
(参考情報)
judge3 / judge4
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 4
other AC * 22 WA * 19
権限があれば一括ダウンロードができます

ソースコード

diff #

import sys

# Set a higher recursion depth limit for potentially deep expression trees.
# The problem constraints allow expression lengths up to 1000, which could lead to
# recursion depths close to that in worst-case scenarios (like deeply nested parentheses).
# Python's default limit is often 1000, so increasing it is safer.
try:
    sys.setrecursionlimit(2000) 
except Exception as e:
    # Some environments might restrict changing the recursion limit.
    # If it fails, we proceed with the default limit and hope it's sufficient.
    pass 

# Read M (maximum value) and ans (target answer)
M, ans = map(int, sys.stdin.readline().split())
# Read the expression string
expr_str = sys.stdin.readline().strip()

# Define a class for Abstract Syntax Tree (AST) nodes
class Node:
    def __init__(self, type, value=None, left=None, right=None):
        self.type = type  # Node type: 'num', '$', or '&'
        self.value = value # Stores the integer value for 'num' nodes
        self.left = left   # Left child node for operator nodes
        self.right = right # Right child node for operator nodes
        # Flag to indicate if this node represents a subexpression originally enclosed in parentheses
        self.is_parenthesized = False 
        # Use object ID for memoization keys, ensuring unique identification for each node instance
        self.id = id(self) 

# Global variable to keep track of the current position in the expression string during parsing
pos = 0
# Flag to indicate if parsing encountered an error (e.g., invalid format)
parsing_failed = False

# Recursive descent parser functions based on the provided BNF grammar

# Parses a factor: either a number or a parenthesized expression
def parse_factor():
    global pos, parsing_failed
    # Prevent reading past the end of the string
    if pos >= len(expr_str):
         parsing_failed = True
         # Return a dummy node to simplify error handling upstream
         return Node('num', value=-1) 

    # Check if the current character indicates the start of a parenthesized expression
    if expr_str[pos] == '(':
        pos += 1 # Consume '('
        # Recursively parse the expression within the parentheses
        node = parse_expression()
        # If parsing the inner expression failed, propagate the error
        if parsing_failed: return node 

        # Expect a closing parenthesis
        if pos >= len(expr_str) or expr_str[pos] != ')':
             parsing_failed = True # Error: Mismatched or missing parenthesis
             return Node('num', value=-1) 
        pos += 1 # Consume ')'
        # Mark the node representing the inner expression as parenthesized.
        # This is important for correct reconstruction of the output string.
        node.is_parenthesized = True 
        return node
    else:
        # Parse a number
        num_str = ""
        start_pos = pos
        # Consume all consecutive digits
        while pos < len(expr_str) and expr_str[pos].isdigit():
            num_str += expr_str[pos]
            pos += 1
        
        # If no digits were found, it's a syntax error (expected number or '(')
        if not num_str:
             parsing_failed = True
             return Node('num', value=-1) 
             
        val = int(num_str)
        # Create a 'num' node. The validity check (0 <= val <= M) is handled later.
        return Node('num', value=val)

# Parses a term: a sequence of factors combined by '&' operators (multiplication/division)
def parse_term():
    global pos, parsing_failed
    # A term starts with a factor
    node = parse_factor()
    if parsing_failed: return node # Propagate parsing errors

    # Repeatedly parse '&' followed by a factor, respecting left-to-right associativity
    while pos < len(expr_str) and expr_str[pos] == '&':
        op_type = '&'
        pos += 1 # Consume '&'
        # Parse the right operand (another factor)
        right_node = parse_factor()
        if parsing_failed: return right_node # Propagate parsing errors
        # Create a new '&' node with the current term expression as left child
        # and the newly parsed factor as right child.
        new_node = Node(op_type, left=node, right=right_node)
        # Update 'node' to be the root of the term evaluated so far
        node = new_node 
    return node

# Parses an expression: a sequence of terms combined by '$' operators (addition/subtraction)
def parse_expression():
    global pos, parsing_failed
    # An expression starts with a term
    node = parse_term()
    if parsing_failed: return node # Propagate parsing errors

    # Repeatedly parse '$' followed by a term, respecting left-to-right associativity
    while pos < len(expr_str) and expr_str[pos] == '$':
        op_type = '$'
        pos += 1 # Consume '$'
        # Parse the right operand (another term)
        right_node = parse_term()
        if parsing_failed: return right_node # Propagate parsing errors
        # Create a new '$' node with the current expression evaluated so far as left child
        # and the newly parsed term as right child.
        new_node = Node(op_type, left=node, right=right_node)
         # Update 'node' to be the root of the expression evaluated so far
        node = new_node
    return node

# --- Main Logic ---

# Build the AST from the input expression string
root = parse_expression()

# Check if parsing failed or did not consume the entire string
if parsing_failed or pos != len(expr_str):
    # The problem statement guarantees valid input format, so this should ideally not happen.
    # If it does, it indicates an issue, output -1.
    print("-1")
    sys.exit()

# Memoization dictionary for the DP computation (possible values for each node)
memo_possible = {}

# Dynamic Programming function (using memoized recursion) to compute possible values for each node
def compute_possible(node):
    # Use the unique node ID as the key for memoization
    node_id = node.id 
    if node_id in memo_possible:
        return memo_possible[node_id] # Return cached result if available

    # Initialize a boolean array to store possible values (indices 0 to M)
    possible = [False] * (M + 1)

    if node.type == 'num':
        # Base case: For a number node, check if its value is within the valid range [0, M]
        if 0 <= node.value <= M:
             possible[node.value] = True # Mark this value as possible
    elif node.type in ['$', '&']:
        # Recursive case: For operator nodes
        # Compute possible values for left and right children
        possible_left = compute_possible(node.left)
        possible_right = compute_possible(node.right)

        # Iterate through all pairs of possible values from children
        for v_left in range(M + 1):
            if possible_left[v_left]: # If v_left is a possible value for the left child
                for v_right in range(M + 1):
                    if possible_right[v_right]: # If v_right is a possible value for the right child
                        
                        # Apply the operations corresponding to the node type ('$' or '&')
                        if node.type == '$':
                            # Try addition (+)
                            res_add = v_left + v_right
                            # Check if result is within the valid range [0, M]
                            if 0 <= res_add <= M: possible[res_add] = True
                            
                            # Try subtraction (-)
                            res_sub = v_left - v_right
                            # Check if result is within the valid range [0, M]
                            if 0 <= res_sub <= M: possible[res_sub] = True
                        
                        elif node.type == '&':
                            # Try multiplication (*)
                            res_mul = v_left * v_right
                             # Check if result is within the valid range [0, M]
                            if 0 <= res_mul <= M: possible[res_mul] = True

                            # Try integer division (//)
                            # Check for division by zero first
                            if v_right != 0: 
                                res_div = v_left // v_right # Use integer division
                                # Check if result is within the valid range [0, M]
                                # Note: Since v_left >= 0 and v_right > 0, res_div >= 0 is guaranteed.
                                if 0 <= res_div <= M: possible[res_div] = True
    
    # Store the computed possible values in the memoization cache before returning
    memo_possible[node_id] = possible
    return possible

# Compute the set of possible values for the root of the AST
root_possible = compute_possible(root)

# Check if the target answer 'ans' is among the possible values for the entire expression
if not root_possible[ans]:
    # If 'ans' is not possible, print -1
    print("-1")
else:
    # If 'ans' is possible, reconstruct one valid assignment string
    # Memoization dictionary for the reconstruction process
    memo_reconstruct = {}

    # Recursive function to reconstruct the expression string for a given node and target value
    def reconstruct(node, target_value):
        # Use a tuple (node ID, target value) as the key for memoization
        state_key = (node.id, target_value)
        if state_key in memo_reconstruct:
            # Return cached result (could be the reconstructed string or None if failed)
            return memo_reconstruct[state_key]

        if node.type == 'num':
             # Base case: If it's a number node, it must match the target value
             if node.value == target_value: 
                return str(node.value) # Return the number as a string
             else: 
                # This path is invalid (should not happen if logic is correct and called based on possible values)
                return None 

        # Recursive case: Operator node
        # Retrieve pre-computed possible values for children from memo_possible cache
        possible_left = memo_possible[node.left.id]
        possible_right = memo_possible[node.right.id]

        # Iterate through possible value pairs from children to find one that yields the target_value
        for v_left in range(M + 1):
            if possible_left[v_left]:
                for v_right in range(M + 1):
                    if possible_right[v_right]:
                        
                        current_op_char = None # Stores the character '+', '-', '*', '/' for the reconstructed op
                        valid_op_found = False # Flag to indicate if a valid operation is found for this pair

                        # Check which operation(s) with v_left and v_right result in target_value
                        if node.type == '$':
                            # Check addition first
                            res_add = v_left + v_right
                            if res_add == target_value and 0 <= res_add <= M:
                                current_op_char = '+'
                                valid_op_found = True
                            
                            # If addition didn't work, check subtraction
                            # The problem asks for any solution, so picking the first one found is fine.
                            if not valid_op_found:
                                res_sub = v_left - v_right
                                if res_sub == target_value and 0 <= res_sub <= M:
                                    current_op_char = '-'
                                    valid_op_found = True

                        elif node.type == '&':
                             # Check multiplication first
                            res_mul = v_left * v_right
                            if res_mul == target_value and 0 <= res_mul <= M:
                                current_op_char = '*'
                                valid_op_found = True
                            
                            # If multiplication didn't work, check division
                            if not valid_op_found:
                                if v_right != 0: # Avoid division by zero
                                    res_div = v_left // v_right
                                    if res_div == target_value and 0 <= res_div <= M:
                                        # Use '/' for output string as specified
                                        current_op_char = '/' 
                                        valid_op_found = True

                        # If a valid operation was found for this pair (v_left, v_right)
                        if valid_op_found:
                             # Recursively reconstruct the strings for left and right children
                             left_str = reconstruct(node.left, v_left)
                             # If reconstruction for the left child failed, this path is invalid, try next pair
                             if left_str is None: continue 
                             
                             right_str = reconstruct(node.right, v_right)
                              # If reconstruction for the right child failed, this path is invalid, try next pair
                             if right_str is None: continue

                             # Both children successfully reconstructed. Combine them with the operator.
                             res_str = f"{left_str}{current_op_char}{right_str}"
                             # If the original subexpression was parenthesized, add parentheses back
                             if node.is_parenthesized:
                                 res_str = f"({res_str})"

                             # Cache the successful reconstruction and return it
                             memo_reconstruct[state_key] = res_str
                             return res_str

        # If the loops complete without finding a valid reconstruction for this (node, target_value)
        memo_reconstruct[state_key] = None # Cache the failure (None)
        return None

    # Start the reconstruction process from the root node with the target answer 'ans'
    final_expr = reconstruct(root, ans)
    
    # Print the final reconstructed expression string.
    # If final_expr is None (which ideally shouldn't happen if root_possible[ans] was true),
    # print -1 as a fallback.
    print(final_expr if final_expr is not None else "-1")
0