Official

E - 倉庫の配置計画 / Warehouse Placement Plan Editorial by admin

Gemini 3.0 Flash (Thinking)

Overview

This problem is a variant of the Maximum Weight Independent Set Problem on graphs. When we consider the \(N\) sections as vertices and the corridors as edges, we select a set of vertices (locations to build warehouses) such that no two adjacent vertices are chosen, and maximize the total weight (total storage capacity). However, if the total exceeds \(M\), then \(M\) is the answer.

Analysis

1. Problem Modeling

Organizing the given constraints in graph theory terms yields the following: - There is an undirected graph with at most \(N \leq 40\) vertices. - Each vertex \(i\) is assigned a weight \(P_i\). - Select a set of mutually non-adjacent vertices (an independent set) and maximize the total weight. - The final answer is \(\min(\text{maximum weight}, M)\).

2. Need for Exponential Time Algorithm

The maximum weight independent set problem is generally NP-hard, and no efficient polynomial-time algorithm has been found. With the constraint \(N=40\), it is impossible to try all combinations of vertices (\(2^N \approx 10^{12}\)), but by using Meet-in-the-middle, which splits the problem in half, it becomes possible to solve it with a computation of about \(2^{N/2} \approx 2^{20} \approx 10^6\).

Algorithm

1. Preparation for Meet-in-the-middle

Split the vertex set into two groups: \(N_1 = \{0, \dots, \lfloor N/2 \rfloor - 1\}\) and \(N_2 = \{\lfloor N/2 \rfloor, \dots, N-1\}\).

2. Processing the First Half (\(N_1\))

Enumerate all independent sets using only vertices in \(N_1\), and compute the total weight for each. Then, using bit DP (SOS DP: Sum Over Subsets technique), compute the following values: - dp[mask] = the maximum weight of an independent set that can be formed using only vertices contained in the subset mask of \(N_1\).

This allows us to retrieve in \(O(1)\) the maximum weight within a given range when we are later given a condition (mask) specifying “these vertices in \(N_1\) are allowed to be used.”

3. Processing the Second Half (\(N_2\)) and Combining

Enumerate each independent set using only vertices in \(N_2\). When a particular independent set \(S_2 \subseteq N_2\) is chosen, the vertices on the \(N_1\) side that can be simultaneously selected are limited to “vertices not adjacent to any vertex in \(S_2\)”.

  1. Compute the total weight \(W_2\) of \(S_2\).
  2. Let adj_mask be the set of vertices on the \(N_1\) side that are adjacent to vertices in \(S_2\).
  3. The mask of available vertices on the \(N_1\) side is valid_mask = all_n1_bits AND NOT adj_mask.
  4. The total weight is \(W_2 + dp[valid\_mask]\).

Compute this value for all \(S_2\) and find the maximum.

Complexity

  • Time Complexity: \(O(N \cdot 2^{N/2})\)
    • Enumerating independent sets for the first half and SOS DP takes \(O(N \cdot 2^{N/2})\).
    • Enumerating the second half and combining with the first half takes \(O(2^{N/2})\).
    • When \(N=40\), \(2^{20} \approx 10^6\), which is well within the time limit (5 seconds) even in Python.
  • Space Complexity: \(O(2^{N/2})\)
    • The dp table stores \(2^{20}\) elements (on the order of a few MB to tens of MB).

Implementation Notes

  • Utilizing Bit Operations: By managing the adjacency matrix with bitmasks, adjacency checks and extraction of available vertices can be performed efficiently.

  • SOS DP (Fast Subset Sum Computation): When updating dp[mask], by iterating over each bit, the maximum value within a set can be propagated efficiently.

  • Early Termination: If the maximum weight has already reached \(M\), we can stop further exploration and output \(M\), reducing execution time.

  • Memory Optimization: Using array.array or bytearray instead of Python’s list can reduce memory usage (not strictly necessary under these constraints, but becomes important for larger \(N\)).

    Source Code

import sys
import array

def solve():
    # Read all input at once
    input_data = sys.stdin.read().split()
    if not input_data:
        return
    
    # Parse N, M, K
    N = int(input_data[0])
    M = int(input_data[1])
    K = int(input_data[2])
    
    # Parse P_i
    P = [int(x) for x in input_data[3:3+N]]
    
    # Build adjacency matrix as bitmasks
    adj = [0] * N
    idx = 3 + N
    for _ in range(K):
        u = int(input_data[idx]) - 1
        v = int(input_data[idx+1]) - 1
        adj[u] |= (1 << v)
        adj[v] |= (1 << u)
        idx += 2
        
    # Split districts into two halves for meet-in-the-middle
    n1 = N // 2
    n2 = N - n1
    
    # Part 1: Compute all independent sets in the first half (N1)
    all_n1 = (1 << n1) - 1
    # dp[mask] will store the maximum weight independent set that is a subset of 'mask'
    dp = array.array('Q', [0] * (1 << n1))
    is_indep1 = bytearray(1 << n1)
    is_indep1[0] = 1 # Empty set is always independent
    
    for i in range(n1):
        bit = 1 << i
        adj_m = adj[i] & (bit - 1)
        p_i = P[i]
        for mask in range(bit):
            if is_indep1[mask] and not (mask & adj_m):
                new_mask = mask | bit
                is_indep1[new_mask] = 1
                dp[new_mask] = dp[mask] + p_i
    
    # SOS DP (Sum Over Subsets) to find the max weight independent set within each mask
    for i in range(n1):
        bit = 1 << i
        for mask in range(0, 1 << n1, bit << 1):
            for x in range(mask, mask + bit):
                if dp[x] > dp[x + bit]:
                    dp[x + bit] = dp[x]
    
    # Current max weight found in N1
    max_w = dp[all_n1]
    if max_w >= M:
        print(M)
        return

    # Part 2: Iterate over independent sets in the second half (N2)
    # and combine them with the best independent set from the first half (N1)
    is_indep2 = bytearray(1 << n2)
    is_indep2[0] = 1
    weight2 = array.array('Q', [0] * (1 << n2))
    adj_to_n1 = array.array('I', [0] * (1 << n2))
    
    for i in range(n2):
        bit = 1 << i
        node_idx = n1 + i
        adj_m_n2 = (adj[node_idx] >> n1) & (bit - 1) # Neighbors in N2 already processed
        adj_m_n1 = adj[node_idx] & all_n1            # Neighbors in N1
        p_val = P[node_idx]
        for mask in range(bit):
            if is_indep2[mask] and not (mask & adj_m_n2):
                new_mask = mask | bit
                is_indep2[new_mask] = 1
                w = weight2[mask] + p_val
                weight2[new_mask] = w
                a = adj_to_n1[mask] | adj_m_n1
                adj_to_n1[new_mask] = a
                
                # Combine current N2 independent set with the best valid N1 independent set
                # Valid N1 nodes are those not adjacent to any node in the N2 set
                res = w + dp[all_n1 & ~a]
                if res > max_w:
                    max_w = res
                    # Early exit if we already reached or exceeded requested amount M
                    if max_w >= M:
                        print(M)
                        return
    
    # Final answer is the minimum of requested total and max capacity found
    print(min(M, max_w))

if __name__ == '__main__':
    solve()

This editorial was generated by gemini-3-flash-thinking.