結果

問題 No.1998 Manhattan Restaurant
ユーザー qwewe
提出日時 2025-05-14 13:16:38
言語 PyPy3
(7.3.15)
結果
TLE  
実行時間 -
コード長 10,441 bytes
コンパイル時間 247 ms
コンパイル使用メモリ 82,772 KB
実行使用メモリ 77,860 KB
最終ジャッジ日時 2025-05-14 13:18:16
合計ジャッジ時間 6,091 ms
ジャッジサーバーID
(参考情報)
judge3 / judge2
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 4
other AC * 11 TLE * 1 -- * 19
権限があれば一括ダウンロードができます

ソースコード

diff #

import sys

# Set higher recursion depth limit if needed for deep graph traversals, although BFS is iterative.
# sys.setrecursionlimit(200000) 

def solve():
    # Read the number of houses
    N = int(sys.stdin.readline())
    
    # Handle base cases: If N is 0 or 1, the minimum max distance is 0.
    # The problem guarantees N >= 1, so N=0 check is mostly for completeness.
    if N <= 1: 
        print(0)
        # Consume coordinate line if N=1
        if N == 1:
             sys.stdin.readline()
        return

    # Read house coordinates from input
    pts_coords = []
    for _ in range(N):
        pts_coords.append(list(map(int, sys.stdin.readline().split())))

    # Transform coordinates from (X, Y) to (U, V) = (X+Y, X-Y).
    # This transformation maps Manhattan distance in (X, Y) plane to 
    # Chebyshev distance (L_infinity) in (U, V) plane.
    # L1((x1,y1), (x2,y2)) = |x1-x2| + |y1-y2|
    # L_inf((u1,v1), (u2,v2)) = max(|u1-u2|, |v1-v2|)
    # where u1=x1+y1, v1=x1-y1 and u2=x2+y2, v2=x2-y2.
    U = [(pts_coords[i][0] + pts_coords[i][1]) for i in range(N)]
    V = [(pts_coords[i][0] - pts_coords[i][1]) for i in range(N)]

    # Find indices of points with minimum/maximum U and V coordinates.
    # These points are called "extremal" points. They define the bounding box of the point set in (U, V) space.
    min_U_idx, max_U_idx = 0, 0
    min_V_idx, max_V_idx = 0, 0

    # Store the actual min/max coordinate values found so far to avoid recomputing U[idx] repeatedly.
    current_min_U, current_max_U = U[0], U[0]
    current_min_V, current_max_V = V[0], V[0]

    # Iterate through points to find the indices corresponding to extremal coordinates.
    for i in range(1, N):
        if U[i] < current_min_U: current_min_U = U[i]; min_U_idx = i
        if U[i] > current_max_U: current_max_U = U[i]; max_U_idx = i
        if V[i] < current_min_V: current_min_V = V[i]; min_V_idx = i
        if V[i] > current_max_V: current_max_V = V[i]; max_V_idx = i

    # Collect the unique indices of the extremal points. There are at most 4 unique indices.
    extremal_indices = {min_U_idx, max_U_idx, min_V_idx, max_V_idx}

    # Define the check function used in binary search.
    # It returns True if it's possible to place two restaurants such that the maximum distance
    # from any house to its nearest restaurant is at most D.
    def check(D):
        # K = 2D is the maximum allowed span (range width/height) of U and V coordinates within a cluster/group.
        # A set of points can be covered by one restaurant R if there exists R' = (u, v) such that
        # max(|Ui - u|, |Vi - v|) <= D for all points i in the set.
        # This is possible if and only if (max Ui - min Ui <= 2D) and (max Vi - min Vi <= 2D).
        K = 2 * D
        
        # Adjacency list for the restricted graph G'.
        # G' is constructed based on the "far apart" relationship, but only considering pairs
        # involving at least one extremal point.
        adj = [[] for _ in range(N)]
        # Use a set to efficiently track edges added to avoid duplicates in adjacency lists
        # and redundant checks.
        edges_added = set() 

        # Build the restricted graph G'
        # Iterate through all points i
        for i in range(N):
             # Check pairs (i, ext_idx) where ext_idx is an extremal point index
             for ext_idx in extremal_indices:
                # Skip self-loops (a point paired with itself)
                if i == ext_idx: continue
                
                # Process each pair {i, ext_idx} only once. Use a sorted tuple as a canonical representation.
                pair = tuple(sorted((i, ext_idx)))
                if pair in edges_added: continue # Skip if this pair has already been processed

                # Check the distance condition: edge exists if points are "far apart" in U or V coordinates.
                if abs(U[i] - U[ext_idx]) > K or abs(V[i] - V[ext_idx]) > K:
                   # Add edge between i and ext_idx in the adjacency list representation
                   adj[i].append(ext_idx)
                   adj[ext_idx].append(i)
                   edges_added.add(pair) # Mark this pair as processed


        # Check if the constructed graph G' is bipartite using Breadth-First Search (BFS).
        # Bipartiteness check also assigns colors (0 or 1) to nodes, representing the two sets (potential clusters).
        colors = {} # Dictionary to store assigned color for visited nodes during BFS
        is_bipartite = True
        
        # Iterate through all nodes to handle potentially disconnected graphs
        for i in range(N):
            # Start BFS from node i if it hasn't been visited (colored) yet
            if i not in colors:
                colors[i] = 0 # Assign an initial color (e.g., 0)
                q = [i] # Initialize BFS queue with the starting node
                
                head = 0 # Use head index to simulate queue dequeuing efficiently
                while head < len(q):
                    curr = q[head]
                    head += 1
                    
                    # Process neighbors of the current node
                    for neighbor in adj[curr]:
                        if neighbor not in colors:
                            # If neighbor is uncolored, assign the opposite color and add to queue
                            colors[neighbor] = 1 - colors[curr]
                            q.append(neighbor)
                        elif colors[neighbor] == colors[curr]:
                            # Conflict detected: an edge connects two nodes of the same color.
                            # This means an odd cycle exists, so the graph is not bipartite.
                            is_bipartite = False
                            break # Exit neighbor loop immediately
                    if not is_bipartite:
                        break # Exit BFS loop for the current component
            if not is_bipartite:
                break # Exit the main loop over nodes since non-bipartiteness is confirmed

        # If G' is not bipartite, then the distance D is too small, return False
        if not is_bipartite:
            return False

        # If G' is bipartite, we proceed to verify the span conditions for the partition found.
        # This partition is derived from the BFS coloring.
        C1_indices = [] # List to store indices of points assigned to cluster 1 (color 0)
        C2_indices = [] # List to store indices of points assigned to cluster 2 (color 1)
        
        # Assign nodes to clusters C1 and C2 based on their assigned colors.
        # Nodes that were not visited by BFS (e.g., isolated vertices or entire components)
        # need to be assigned as well. We can arbitrarily assign them to C1.
        for i in range(N):
             if i in colors: # If node i was colored during BFS
                if colors[i] == 0:
                    C1_indices.append(i)
                else:
                    C2_indices.append(i)
             else: 
                 # Assign uncolored nodes to C1 (this handles isolated nodes/components)
                 colors[i] = 0 # Record the assignment for consistency
                 C1_indices.append(i)

        # Check span condition for cluster C1
        if C1_indices: # Only check if the cluster is non-empty
            # Compute the bounding box (min/max U and V coordinates) for points in C1
            min_U1 = U[C1_indices[0]]
            max_U1 = U[C1_indices[0]]
            min_V1 = V[C1_indices[0]]
            max_V1 = V[C1_indices[0]]
            # Optimize finding min/max by iterating from the second element
            for idx in C1_indices[1:]:
                u_val, v_val = U[idx], V[idx]
                min_U1 = min(min_U1, u_val)
                max_U1 = max(max_U1, u_val)
                min_V1 = min(min_V1, v_val)
                max_V1 = max(max_V1, v_val)
            
            # Check if the U-span (maxU - minU) or V-span (maxV - minV) exceeds the threshold K
            if max_U1 - min_U1 > K or max_V1 - min_V1 > K:
                return False # Span condition failed for C1

        # Check span condition for cluster C2
        if C2_indices: # Only check if the cluster is non-empty
             # Compute the bounding box for points in C2
             min_U2 = U[C2_indices[0]]
             max_U2 = U[C2_indices[0]]
             min_V2 = V[C2_indices[0]]
             max_V2 = V[C2_indices[0]]
             # Optimize finding min/max
             for idx in C2_indices[1:]:
                 u_val, v_val = U[idx], V[idx]
                 min_U2 = min(min_U2, u_val)
                 max_U2 = max(max_U2, u_val)
                 min_V2 = min(min_V2, v_val)
                 max_V2 = max(max_V2, v_val)

             # Check if the U-span or V-span exceeds the threshold K
             if max_U2 - min_U2 > K or max_V2 - min_V2 > K:
                 return False # Span condition failed for C2

        # If both G' bipartiteness and span conditions for the partition hold, D is feasible.
        return True

    
    # Perform binary search on the answer D. The answer is guaranteed to be an integer.
    low = 0 # Minimum possible distance is 0
    
    # Calculate an initial upper bound for D.
    # The maximum distance needed if all points were covered by a single restaurant is ceil(max_span / 2).
    # This serves as a valid upper bound for the two-restaurant case.
    max_span_U = 0
    if N > 0 : max_span_U = current_max_U - current_min_U # Use pre-calculated min/max U values
    max_span_V = 0
    if N > 0 : max_span_V = current_max_V - current_min_V # Use pre-calculated min/max V values
    
    # The upper bound is ceil(max(span_U, span_V) / 2)
    high = (max(max_span_U, max_span_V) + 1) // 2 
    ans = high # Initialize answer with this upper bound

    # Standard binary search loop to find the minimum feasible D
    while low <= high:
        mid = (low + high) // 2
        if check(mid): # If distance `mid` is feasible
            ans = mid       # Store `mid` as the best answer found so far
            high = mid - 1  # Try to find an even smaller feasible D
        else: # If distance `mid` is not feasible
            low = mid + 1   # Need a larger D to satisfy conditions

    # Output the minimum feasible distance D found
    print(ans)

# Execute the solve function to run the program
solve()
0