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 & -bitsand removing it withbits &= bits - 1allows us to efficiently enumerate only the unvisited houses, avoiding unnecessary loop iterations.Pruning: By skipping early when
dp[mask][u] == INFor when \(u\) is not included inmask, 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.
投稿日時:
最終更新: