公式

E - 配達ルートの最適化 / Optimizing Delivery Routes 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

Starting from house \(1\), we need to determine the order to visit all \(N\) houses exactly once, minimizing the total travel cost. Given the constraint \(N \leq 20\), we solve this using bitmask DP.

Analysis

Essence of the Problem

This problem asks us to find the minimum-cost Hamiltonian path starting from house \(1\) and visiting every house exactly once.

Naive Approach

Enumerating all possible visit orders gives \((N-1)!\) permutations. When \(N = 20\), \((N-1)! = 19! \approx 1.2 \times 10^{17}\), which is far too large to handle.

Key Insight

When deciding the visit order, the only information needed to determine which house to visit next is:

  • Which house we are currently at (to calculate the next travel cost)
  • Which houses have already been visited (since we can only go to unvisited houses)

These two pieces of information are sufficient. The detailed order in which houses were visited is irrelevant. By exploiting this property, we can use a DP where the set of visited houses is represented as a bitmask.

Algorithm

We use bitmask DP (Traveling Salesman Problem-style DP).

State Definition

\[dp[\text{mask}][u] = \text{minimum cost when the set of visited houses is mask and we are currently at house } u\]

Here, \(\text{mask}\) is an \(N\)-bit integer where the \(i\)-th bit being set indicates that house \(i\) (0-indexed) has been visited.

Initial State

Since we start from house \(1\) (house \(0\) in 0-indexed):

\[dp[1][0] = 0\]

(Bitmask \(1 = 0\text{b}0\ldots01\) represents the state where only house \(0\) has been visited)

Transition

From state \(dp[\text{mask}][u]\), we move to an unvisited house \(v\) (where the \(v\)-th bit of \(\text{mask}\) is \(0\)):

\[dp[\text{mask} \mid (1 \ll v)][v] = \min\left(dp[\text{mask} \mid (1 \ll v)][v],\; dp[\text{mask}][u] + |V_u - V_v| \times |u - v|\right)\]

Answer

In the state where all houses have been visited (\(\text{mask} = 2^N - 1\)), the last house we are at can be any house, so:

\[\text{Answer} = \min_{u=0}^{N-1} dp[2^N - 1][u]\]

Concrete Example (\(N = 3\), \(V = [10, 30, 20]\))

  • Initial: \(dp[\text{001}][0] = 0\) (at house 0)
  • House 0 → House 1: cost \(|10-30| \times |0-1| = 20\), \(dp[\text{011}][1] = 20\)
  • House 0 → House 2: cost \(|10-20| \times |0-2| = 20\), \(dp[\text{101}][2] = 20\)
  • House 1 → House 2: cost \(|30-20| \times |1-2| = 10\), \(dp[\text{111}][2] = 30\)
  • House 2 → House 1: cost \(|20-30| \times |2-1| = 10\), \(dp[\text{111}][1] = 30\)
  • Answer: \(\min(30, 30) = 30\)

Complexity

  • Time complexity: \(O(2^N \times N^2)\) — For each state (\(2^N \times N\) total), we attempt transitions to each unvisited house (up to \(N\))
  • Space complexity: \(O(2^N \times N)\) — Size of the DP table

When \(N = 20\), this amounts to approximately \(2^{20} \times 20^2 \approx 4.2 \times 10^8\) operations.

Implementation Notes

  • Precomputing costs: By precomputing \(\text{cost}[i][j]\), we speed up the computation during transitions.

  • Enumerating unvisited vertices using bit operations: The technique of extracting the lowest set bit with bits & -bits and removing it with bits &= bits - 1 allows us to efficiently enumerate only the unvisited houses, avoiding unnecessary loop iterations.

  • Pruning: By skipping early when dp[mask][u] == INF or when \(u\) is not included in mask, we eliminate unnecessary computations.

    Source Code

import sys

def main():
    input_data = sys.stdin.read().split()
    N = int(input_data[0])
    V = [int(input_data[i+1]) for i in range(N)]
    
    if N == 1:
        print(0)
        return
    
    # Precompute costs
    cost = [[0]*N for _ in range(N)]
    for i in range(N):
        for j in range(N):
            cost[i][j] = abs(V[i] - V[j]) * abs(i - j)
    
    # Bitmask DP
    # dp[mask][i] = minimum cost to have visited exactly the houses in mask,
    #               currently at house i
    # Start: mask has bit 0 set, current house is 0
    
    INF = float('inf')
    full = 1 << N
    dp = [[INF]*N for _ in range(full)]
    dp[1][0] = 0  # visited house 0 (1-indexed house 1), at house 0
    
    for mask in range(1, full):
        for u in range(N):
            if dp[mask][u] == INF:
                continue
            if not (mask & (1 << u)):
                continue
            # Try to visit next unvisited house
            remaining = full - 1 - mask  # bits not in mask
            bits = remaining
            while bits:
                v = (bits & -bits).bit_length() - 1
                new_mask = mask | (1 << v)
                new_cost = dp[mask][u] + cost[u][v]
                if new_cost < dp[new_mask][v]:
                    dp[new_mask][v] = new_cost
                bits &= bits - 1
    
    print(min(dp[full-1]))

main()

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

投稿日時:
最終更新: