公式

E - 展示会の出展計画 / Exhibition Booth Planning 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

From \(N\) exhibit items, select items whose total weight does not exceed \(W\), and maximize “total expected profit \(-\) delivery cost (square of the number of distinct venues visited)”. Leveraging the constraint that the number of venues \(K \leq 8\), we enumerate subsets of venues using bitmasks while solving a knapsack problem.

Analysis

Issues with the Naive Approach

In a standard 0/1 knapsack, we would need to determine “the number of distinct venues \(c\)” from “which items were selected” and subtract the cost \(c^2\). If we include “the set of used venues” in the knapsack DP state, the number of states becomes \(O(2^K \cdot W)\), and with \(O(N)\) transitions, the overall complexity becomes extremely large.

Key Insight

“Decide the set of venues to use in advance” is an effective approach.

If we fix a subset of venues \(S\): - The usable items are limited to “those exhibited at venues in \(S\)” - The delivery cost is fixed at \(|S|^2\)

Therefore, for each \(S\), we solve a “standard 0/1 knapsack using only items belonging to venues in \(S\)”, and subtract \(|S|^2\) from the resulting maximum profit.

Incremental DP Construction

Since \(K \leq 8\), there are at most \(2^8 = 256\) subsets. Solving the knapsack from scratch for each subset would be slow, but we can construct it incrementally.

Find the lowest set bit of subset \(S\) (some venue \(b\)), and let \(S' = S \setminus \{b\}\): - The knapsack result for \(S'\) has already been computed - We can obtain the knapsack result for \(S\) by simply adding the items from venue \(b\)

This way, for each subset, we only need to add items from a single venue.

Algorithm

  1. Group items by venue
  2. Define dp_arr[mask][w] = maximum profit obtainable with weight at most \(w\) from items belonging to the venue set mask
  3. dp_arr[0] = [0, 0, ..., 0] (empty set yields profit 0)
  4. Process mask from \(1\) to \(2^K - 1\) in order:
    • Find the lowest set bit \(b\) of mask, and let prev = mask ^ (1 << b)
    • Copy dp_arr[prev], then perform standard 0/1 knapsack updates with items from venue \(b\)
    • Answer candidate: dp_arr[mask][W] - (popcount of mask)^2
  5. Output the maximum among all subset candidates (at least \(0\))

Complexity

  • Time complexity: \(O(2^K \cdot N \cdot W)\)
    • Each item is processed in approximately \(2^{K-1}\) subsets where its venue is the lowest bit, with \(O(W)\) updates each time
    • In practice, this is sufficiently fast for \(K=8, N=300, W=3000\)
  • Space complexity: \(O(2^K \cdot W)\)
    • Storing knapsack arrays for all subsets (approximately \(256 \times 3001\))

Implementation Notes

  • Getting the lowest set bit: Efficiently obtained with lsb = mask & (-mask), then converted to a venue number with bit_length() - 1

  • Direction of knapsack updates: Since this is a 0/1 knapsack, iterate weights from large to small (to prevent multiple selections of the same item)

  • Lower bound of the answer is 0: Selecting no items (empty set) guarantees a profit of 0, so the answer is always non-negative

  • 0-indexing venue numbers: Input is 1-indexed, but internally converted to 0-indexed for bit operations

    Source Code

import sys

def main():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    K = int(input_data[idx]); idx += 1
    W = int(input_data[idx]); idx += 1
    
    items = []
    for i in range(N):
        w = int(input_data[idx]); idx += 1
        v = int(input_data[idx]); idx += 1
        d = int(input_data[idx]); idx += 1
        items.append((w, v, d - 1))  # 0-indexed venue
    
    # Group items by venue
    venue_items = [[] for _ in range(K)]
    for w, v, d in items:
        venue_items[d].append((w, v))
    
    # For each subset of venues, solve a knapsack problem using only items from those venues
    # and compute max profit - popcount(subset)^2
    # K <= 8, so 2^8 = 256 subsets
    # For each subset, we need to solve a 0/1 knapsack with capacity W
    
    # Precompute for each venue, the items
    # For efficiency: iterate over subsets in a smart order
    # We can use the fact that subset S can be built from subset S without one venue
    # dp_knapsack[S][w] = max value using items from venues in S with total weight exactly w
    # But storing 256 * 2001 might be fine, and we build incrementally
    
    # Actually, let's think about it differently:
    # For each subset S, knapsack[S][w] = best value achievable with weight <= w using items from venues in S
    # We can compute knapsack[S] from knapsack[S without highest bit] by adding items from the highest bit venue
    
    # dp[S] = knapsack array of size W+1 for subset S
    # This requires 256 * 2001 storage = ~512K entries, which is fine
    
    # Build in order of subsets. For empty set, dp[0] = [0] * (W+1)
    # For subset S, find lowest set bit b, let S' = S ^ (1 << b)
    # dp[S] = dp[S'] then add all items from venue b (standard 0/1 knapsack update)
    
    best_answer = 0
    
    # Store dp as a dict of arrays to save memory (only need to keep what's needed)
    # Actually 256 arrays of 2001 ints is fine
    dp = [None] * (1 << K)
    dp[0] = bytearray(b'\x00' * (W + 1) * 4)  # Not great, let's use list approach
    
    # Let's use a simpler approach: just arrays
    # dp[mask] = list of length W+1, dp[mask][w] = max value with weight exactly w? 
    # Better: dp[mask][w] = max value with weight <= w
    
    # For memory, store as list of lists
    dp_arr = [None] * (1 << K)
    dp_arr[0] = [0] * (W + 1)
    
    for mask in range(1, 1 << K):
        # Find lowest set bit
        lsb = mask & (-mask)
        b = lsb.bit_length() - 1
        prev_mask = mask ^ lsb
        
        # Copy from prev_mask
        cur = dp_arr[prev_mask][:]
        
        # Add items from venue b (standard 0/1 knapsack, iterate items, weight backward)
        for w_item, v_item in venue_items[b]:
            for cap in range(W, w_item - 1, -1):
                val = cur[cap - w_item] + v_item
                if val > cur[cap]:
                    cur[cap] = val
        
        dp_arr[mask] = cur
        
        # Compute answer for this mask
        c = bin(mask).count('1')
        cost = c * c
        profit = cur[W] - cost
        if profit > best_answer:
            best_answer = profit
    
    print(best_answer)

main()

This editorial was generated by claude4.6opus-thinking.