結果

問題 No.5022 XOR Printer
ユーザー prussian_coder
提出日時 2025-07-08 16:09:02
言語 PyPy3
(7.3.15)
結果
AC  
実行時間 1,779 ms / 2,000 ms
コード長 19,243 bytes
コンパイル時間 813 ms
コンパイル使用メモリ 81,988 KB
実行使用メモリ 105,508 KB
スコア 5,223,674,328
最終ジャッジ日時 2025-07-26 12:33:15
合計ジャッジ時間 79,771 ms
ジャッジサーバーID
(参考情報)
judge4 / judge1
純コード判定しない問題か言語
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
other AC * 50
権限があれば一括ダウンロードができます

ソースコード

diff #

from __future__ import annotations

import random
import sys
from typing import Optional

random.seed(0)

# Constants
MAX_BIT = 20
MAX_SEE_BIT = 8
DEFAULT_TARGET_BIT = 19
ANNEALING_DECAY = 0.99
INITIAL_ACCEPT_PROB = 1.0

# Input parsing
N, T = map(int, input().split())
N2 = N * N
board: list[int] = []
for _ in range(N):
    board.extend(list(map(int, input().split())))
BINARY_VALS = [1 << i for i in range(MAX_BIT + 1)]
BIT_MASK = BINARY_VALS[-1] - 1


def to_2d(position: int) -> tuple[int, int]:
    """Convert 1D position to 2D coordinates."""
    return position // N, position % N


def to_1d(x: int, y: int) -> int:
    """Convert 2D coordinates to 1D position."""
    return x * N + y


def manhattan_distance(pos1: int, pos2: int) -> int:
    """Calculate Manhattan distance between two positions."""
    x1, y1 = to_2d(pos1)
    x2, y2 = to_2d(pos2)
    return abs(x1 - x2) + abs(y1 - y2)


def generate_move_commands(start: int, end: int) -> list[str]:
    """Generate movement commands from start to end position.

    Args:
        start: Starting position (1D index)
        end: Ending position (1D index)

    Returns:
        List of movement commands ('U', 'D', 'L', 'R')
    """
    commands: list[str] = []
    x1, y1 = to_2d(start)
    x2, y2 = to_2d(end)

    current_x, current_y = x1, y1

    # Move vertically first
    while current_x < x2:
        commands.append("D")
        current_x += 1
    while current_x > x2:
        commands.append("U")
        current_x -= 1

    # Move horizontally
    while current_y < y2:
        commands.append("R")
        current_y += 1
    while current_y > y2:
        commands.append("L")
        current_y -= 1

    return commands


def process_board(
    board: list[int], commands: list[str], s: int, pos: int
) -> tuple[list[int], int, int]:
    """Process board by executing commands."""
    for command in commands:
        if command == "W":
            board[pos] ^= s
        elif command == "C":
            s ^= board[pos]
        elif command == "D":
            pos += N
        elif command == "U":
            pos -= N
        elif command == "R":
            pos += 1
        elif command == "L":
            pos -= 1
    return board, s, pos


def revert_board(board: list[int], command: str, s: int, pos: int) -> tuple[list[int], int, int]:
    """Revert board to the state before executing command."""
    if command == "W":
        board[pos] ^= s
    elif command == "C":
        s ^= board[pos]
    elif command == "D":
        pos -= N
    elif command == "U":
        pos += N
    elif command == "R":
        pos -= 1
    elif command == "L":
        pos += 1
    return board, s, pos


def get_one_intermediate_positions_2d(start_pos: int, end_pos: int) -> list[tuple[int, int]]:
    """Get intermediate position pairs for 2D coordinates."""
    start_x, start_y = to_2d(start_pos)
    end_x, end_y = to_2d(end_pos)
    if start_x > end_x:
        start_x, end_x = end_x, start_x
    if start_y > end_y:
        start_y, end_y = end_y, start_y

    candidates = []
    for x in range(start_x, end_x + 1):
        for y in range(start_y, end_y + 1):
            if x != end_x or y != end_y:
                candidates.append(to_1d(x, y))
    return candidates


def get_two_intermediate_positions_1d(start: int, end: int) -> list[tuple[int, int]]:
    """Get intermediate position pairs for 1D coordinates."""
    if start <= end:
        left, right = start, end
    else:
        left, right = -start, -end

    return [(abs(m1), abs(m2)) for m1 in range(left, right + 1) for m2 in range(m1, right + 1)]


def get_two_intermediate_positions_2d(start_pos: int, end_pos: int) -> list[tuple[int, int]]:
    """Get intermediate position pairs for 2D coordinates."""
    start_x, start_y = to_2d(start_pos)
    end_x, end_y = to_2d(end_pos)

    x_pairs = get_two_intermediate_positions_1d(start_x, end_x)
    y_pairs = get_two_intermediate_positions_1d(start_y, end_y)

    candidates = []
    for x1, x2 in x_pairs:
        for y1, y2 in y_pairs:
            pos1 = to_1d(x1, y1)
            pos2 = to_1d(x2, y2)
            if pos1 != pos2 and pos2 != end_pos:
                candidates.append((pos1, pos2))

    return candidates


def is_valid_state(state: int, target_bit: int) -> bool:
    """Check if state is valid for the given target bit."""
    return BINARY_VALS[target_bit] <= state < BINARY_VALS[target_bit + 1]


class RouteOptimizer:
    """Optimizes routes using 2-opt hill climbing with simulated annealing."""

    def generate_optimal_route(
        self, start_pos: int, board: list[int], target_bit: int
    ) -> list[int]:
        """Generate optimized route to visit all targets.

        Args:
            start_pos: Starting position
            board: Game board state
            target_bit: Target bit for optimization

        Returns:
            Optimized route as list of positions
        """
        targets = self._find_target_positions(board, target_bit)
        initial_route = self._build_nearest_neighbor_route(start_pos, targets, target_bit)
        optimized_route = self._apply_2opt_optimization(initial_route, target_bit)
        return optimized_route

    def _find_target_positions(self, board: list[int], target_bit: int) -> list[int]:
        """Find all positions that need to be visited."""
        return [i for i in range(N2) if (board[i] >> target_bit) & 1 == 0]

    def _build_nearest_neighbor_route(
        self, start_pos: int, targets: list[int], target_bit: int
    ) -> list[int]:
        """Build initial route using nearest neighbor heuristic."""
        route = [start_pos]
        current_pos = start_pos
        remaining_targets = set(targets)

        # Build route using nearest neighbor
        while remaining_targets:
            nearest = min(remaining_targets, key=lambda x: manhattan_distance(current_pos, x))
            route.append(nearest)
            remaining_targets.remove(nearest)
            current_pos = nearest

        # Ensure the last position can be the final destination
        self._adjust_final_position(route, target_bit)
        return route

    def _adjust_final_position(self, route: list[int], target_bit: int) -> None:
        """Adjust route to ensure last position is valid as final destination."""
        for i in reversed(range(len(route))):
            if self._can_be_final_position(board[route[i]], target_bit):
                route[i], route[-1] = route[-1], route[i]
                break

    def _apply_2opt_optimization(self, route: list[int], target_bit: int) -> list[int]:
        """Apply 2-opt optimization with simulated annealing."""
        accept_probability = INITIAL_ACCEPT_PROB

        while True:
            improved = False
            indices = list(range(1, len(route) + 1))
            random.shuffle(indices)

            for i, idx1 in enumerate(indices):
                for idx2 in indices[i + 1 :]:
                    if idx1 > idx2:
                        idx1, idx2 = idx2, idx1

                    if not self._is_valid_2opt_move(route, idx1, idx2, target_bit):
                        continue

                    delta = self._calculate_2opt_improvement(route, idx1, idx2)

                    if self._should_accept_move(delta, accept_probability):
                        route[idx1:idx2] = reversed(route[idx1:idx2])
                        improved = True
                        break

                if improved:
                    break

            if not improved:
                break

            accept_probability *= ANNEALING_DECAY

        return route

    def _is_valid_2opt_move(self, route: list[int], idx1: int, idx2: int, target_bit: int) -> bool:
        """Check if 2-opt move is valid."""
        return not (
            idx2 == len(route) and not self._can_be_final_position(board[route[idx1]], target_bit)
        )

    def _calculate_2opt_improvement(self, route: list[int], idx1: int, idx2: int) -> int:
        """Calculate distance improvement from 2-opt move."""
        if idx2 == len(route):
            return manhattan_distance(route[idx1 - 1], route[idx2 - 1]) - manhattan_distance(
                route[idx1 - 1], route[idx1]
            )

        return (
            manhattan_distance(route[idx1 - 1], route[idx2 - 1])
            + manhattan_distance(route[idx1], route[idx2])
            - manhattan_distance(route[idx1 - 1], route[idx1])
            - manhattan_distance(route[idx2 - 1], route[idx2])
        )

    def _should_accept_move(self, delta: int, accept_probability: float) -> bool:
        """Determine if a move should be accepted."""
        return delta < 0 or (delta == 0 and random.random() < accept_probability)

    def _can_be_final_position(self, cell_value: int, target_bit: int) -> bool:
        """Check if position can be the final destination."""
        return target_bit == DEFAULT_TARGET_BIT or is_valid_state(cell_value ^ BIT_MASK, target_bit)


class WriteCopyPlanner:
    """Plans write and copy operations for optimal path execution."""

    def __init__(self, target_bit: int):
        self.target_bit = target_bit
        self.see_bit = min(target_bit, MAX_SEE_BIT)

    def generate_execution_plan(
        self, route: list[int], board: list[int], initial_state: int
    ) -> list[str]:
        """Generate complete execution plan for the route.

        Args:
            route: Route to execute
            board: Game board state
            initial_state: Initial state value

        Returns:
            List of commands to execute
        """
        # Initialize dynamic programming tables
        dp_table, transition_table = self._initialize_dp_tables(len(route))

        # Find optimal starting states
        start_states = self._find_optimal_start_states(route, board, initial_state, dp_table)

        # Calculate board top bits for efficiency
        board_tops = self._calculate_board_tops(board)

        # Fill DP table
        self._fill_dp_table(route, board, dp_table, transition_table, board_tops)

        # Generate commands from optimal path
        commands = self._reconstruct_commands(route, dp_table, transition_table, start_states)

        return commands

    def _initialize_dp_tables(
        self, route_length: int
    ) -> tuple[list[list[float]], list[list[tuple]]]:
        """Initialize dynamic programming tables."""
        states_count = 1 << self.see_bit
        dp_table = [[float("inf")] * states_count for _ in range(route_length - 1)]
        transition_table = [[(None, None, None)] * states_count for _ in range(route_length - 1)]
        return dp_table, transition_table

    def _find_optimal_start_states(
        self, route: list[int], board: list[int], initial_state: int, dp_table: list[list[float]]
    ) -> list[Optional[int]]:
        """Find optimal starting states for each state configuration."""
        start_states = [None] * (1 << self.see_bit)
        start_pos = route[0]

        for pos in range(N2):
            if not is_valid_state(board[pos] ^ initial_state, self.target_bit):
                continue

            state = board[pos] ^ initial_state
            state_top = self._extract_top_bits(state)
            distance = manhattan_distance(start_pos, pos) + manhattan_distance(pos, route[1])

            if dp_table[0][state_top] > distance:
                dp_table[0][state_top] = distance
                start_states[state_top] = pos

        return start_states

    def _calculate_board_tops(self, board: list[int]) -> list[int]:
        """Calculate top bits for all board positions."""
        return [
            (value % (1 << self.target_bit)) // (1 << (self.target_bit - self.see_bit))
            for value in board
        ]

    def _extract_top_bits(self, state: int) -> int:
        """Extract top bits from state."""
        return (state % (1 << self.see_bit)) // (1 << (self.target_bit - self.see_bit))

    def _fill_dp_table(
        self,
        route: list[int],
        board: list[int],
        dp_table: list[list[float]],
        transition_table: list[list[tuple]],
        board_tops: list[int],
    ) -> None:
        """Fill the dynamic programming table."""
        for i in range(1, len(route) - 1):
            for state in range(1 << self.see_bit):
                if dp_table[i - 1][state] == float("inf"):
                    continue

                # Direct write without copy
                self._process_direct_write(i, state, route, board_tops, dp_table, transition_table)

                # Write with intermediate copies
                if i > 1:
                    self._process_copy_operations(
                        i, state, route, board, board_tops, dp_table, transition_table
                    )

    def _process_direct_write(
        self,
        i: int,
        state: int,
        route: list[int],
        board_tops: list[int],
        dp_table: list[list[float]],
        transition_table: list[list[tuple]],
    ) -> None:
        """Process direct write operation."""
        current_state = state
        value = board_tops[route[i]] ^ current_state
        cost = dp_table[i - 1][state] - self._calculate_bit_score(value)

        if dp_table[i][current_state] > cost:
            dp_table[i][current_state] = cost
            transition_table[i][current_state] = (state, None, None)

    def _process_copy_operations(
        self,
        i: int,
        state: int,
        route: list[int],
        board: list[int],
        board_tops: list[int],
        dp_table: list[list[float]],
        transition_table: list[list[tuple]],
    ) -> None:
        """Process copy operations at intermediate positions."""
        for pos1, pos2 in get_two_intermediate_positions_2d(route[i - 1], route[i]):
            new_state = self._calculate_copy_state(
                state, pos1, pos2, route[i - 1], board, board_tops
            )
            if new_state is None:
                continue

            value = board_tops[route[i]] ^ new_state
            cost = dp_table[i - 1][state] + 2 - self._calculate_bit_score(value)

            if dp_table[i][new_state] > cost:
                dp_table[i][new_state] = cost
                transition_table[i][new_state] = (state, pos1, pos2)

    def _calculate_copy_state(
        self,
        state: int,
        pos1: int,
        pos2: int,
        route_pos: int,
        board: list[int],
        board_tops: list[int],
    ) -> Optional[int]:
        """Calculate new state after copy operations."""
        if pos1 == route_pos:
            if board[pos2] ^ BIT_MASK >= BINARY_VALS[self.target_bit] or not is_valid_state(
                board[pos1] ^ board[pos2], self.target_bit
            ):
                return None
            return board_tops[pos1] ^ board_tops[pos2]
        else:
            if (
                board[pos1] ^ BIT_MASK >= BINARY_VALS[self.target_bit]
                or board[pos2] ^ BIT_MASK >= BINARY_VALS[self.target_bit]
            ):
                return None
            return state ^ board_tops[pos1] ^ board_tops[pos2]

    def _reconstruct_commands(
        self,
        route: list[int],
        dp_table: list[list[float]],
        transition_table: list[list[tuple]],
        start_states: list[Optional[int]],
    ) -> list[str]:
        """Reconstruct commands from optimal path."""
        commands = []
        optimal_state = dp_table[-1].index(min(dp_table[-1]))

        # Reconstruct path backwards
        for i in reversed(range(2, len(route) - 1)):
            commands.append("W")
            prev_state, pos1, pos2 = transition_table[i][optimal_state]

            if pos1 is None:
                commands.extend(generate_move_commands(route[i - 1], route[i]))
            else:
                self._add_copy_commands(commands, route[i - 1], pos1, pos2, route[i])

            optimal_state = prev_state

        # Add initial commands
        commands.append("W")
        initial_pos = start_states[optimal_state]
        commands.extend(generate_move_commands(initial_pos, route[1]))
        commands.append("C")
        commands.extend(generate_move_commands(route[0], initial_pos))
        commands.reverse()

        # Add final movement and copy
        commands.extend(generate_move_commands(route[-2], route[-1]))
        if self.target_bit == 19:
            commands.append("W")
        else:
            commands.append("C")

        return commands

    def _add_copy_commands(
        self, commands: list[str], start_pos: int, pos1: int, pos2: int, end_pos: int
    ) -> None:
        """Add copy-related movement commands."""
        commands.extend(generate_move_commands(pos2, end_pos))
        commands.append("C")
        commands.extend(generate_move_commands(pos1, pos2))
        commands.append("C")
        commands.extend(generate_move_commands(start_pos, pos1))

    def _calculate_bit_score(self, state_top: int) -> int:
        """Calculate bit score for state optimization."""
        count = 0
        for i in reversed(range(self.see_bit)):
            if not (state_top >> i & 1):
                return count
            count += 1
        return count


def postprocess_board(
    board: list[int], commands: list[str], s: int, pos: int
) -> tuple[list[int], int, int]:
    """Postprocess board to optimize it."""
    while True:
        min_pos = board.index(min(board))
        necessary_steps = len(commands) + manhattan_distance(pos, min_pos) + 4
        if necessary_steps > T:
            last_command = commands.pop()
            board, s, pos = revert_board(board, last_command, s, pos)
            continue
        best_s = 10**6
        best_v = None
        for v in get_one_intermediate_positions_2d(pos, min_pos):
            s2 = board[v] ^ s
            if s2 > best_s:
                best_s = s2
                best_v = v
        if best_v is None:
            last_command = commands.pop()
            board, s, pos = revert_board(board, last_command, s, pos)
            continue
        added_commands = (
            generate_move_commands(pos, best_v)
            + ["C"]
            + generate_move_commands(best_v, min_pos)
            + ["W", "C", "W"]
        )
        commands.extend(added_commands)
        board, s, pos = process_board(board, added_commands, s, pos)

        return board, s, pos, commands


def solve_puzzle(board: list[int]) -> None:
    """Main puzzle solving function."""
    pos = to_1d(0, 0)
    s = 0
    commands = []

    # Generate optimal route
    for target_bit in reversed(range(MAX_BIT)):
        route_optimizer = RouteOptimizer()
        optimal_route = route_optimizer.generate_optimal_route(pos, board, target_bit)

        # Generate execution plan
        planner = WriteCopyPlanner(target_bit)
        execution_commands = planner.generate_execution_plan(optimal_route, board, s)
        board, s, pos = process_board(board, execution_commands, s, pos)
        commands.extend(execution_commands)
        if len(commands) > T:
            break

    board, s, pos, commands = postprocess_board(board, commands, s, pos)

    for command in commands[:T]:
        print(command)
    print(sum(board), file=sys.stderr)


solve_puzzle(board)
0