E - 配達ルートの最適化 / Optimizing Delivery Routes 解説 by admin
GPT 5.2 HighOverview
Starting from house \(1\), we need to find the visiting order (Hamiltonian path) that visits every house exactly once while minimizing the total travel cost \(|V_i-V_j|\times|i-j|\). Since \(N\le 20\), this can be solved with subset DP (bit DP).
Analysis
Key Observations
- Since “you can move directly to any unvisited house,” the only constraint on the path is the visiting order.
- In essence, this is “the shortest Hamiltonian path on a weighted complete graph (fixed start, free endpoint).”
- The cost is symmetric, and the weight of each edge can be precomputed as: $\( w(i,j)=|V_i-V_j|\times|i-j| \)$
Why Brute Force Doesn’t Work
Exhaustively searching all visiting orders yields \((N-1)!\) permutations (rearrangements of all houses except house 1).
For example, when \(N=20\), \(19!\approx 1.2\times 10^{17}\), which is far too large.
Solution Approach
Only “which set of houses has been visited” and “the last house visited” are needed for the next transition (the entire past order is unnecessary).
Using this property, we perform state-compressed DP (bit DP).
Algorithm
State
Since house \(1\) is already visited from the start, we track the visitation status of houses \(2..N\) using bits.
- \(M=N-1\) (the number of houses \(2..N\))
mask: an \(M\)-bit integer
- bit \(k\) (\(0\)-indexed) is 1 ⇔ house \((k+2)\) has been visited
last: the current house (0-indexed, \(0..N-1\))last=0represents house 1,last=1represents house 2, …
The DP is defined as: $\( dp[\text{mask}][\text{last}] = \text{"minimum cost to start from house 1, visit all houses in mask, and end at last"} \)$
Initial state:
- Houses \(2..N\) have not been visited yet, so mask=0
- The current location is house 1, so last=0
- Therefore \(dp[0][0]=0\)
Transition
Choose one unvisited house nxt and move to it.
- newmask = mask | (bit corresponding to nxt)
- The transition cost is the precomputed \(w(last,nxt)\)
Written as a formula: $\( dp[newmask][nxt] = \min\left(dp[newmask][nxt],\ dp[mask][last] + w(last,nxt)\right) \)$
Answer
The fully visited state is full = (1<<M)-1. Since the endpoint is free:
$\(
\min_{last\in\{2..N\}} dp[full][last]
\)\(
is the answer (when \)N=1$, there is no movement, so the answer is 0).
Concrete Example (\(N=3\))
The visitation status of houses 2 and 3 is stored in 2 bits:
- Start from mask=00 (no one visited yet)
- For example, going to house 2 gives mask=01, last = house 2
- Then going to house 3 gives mask=11, last = house 3
In this way, updates depend only on the visited set and the last house.
Complexity
- Time complexity: \(O(N^2 \cdot 2^{N-1})\)
For eachmask(\(2^{N-1}\) possibilities), we transition fromlast(up to \(N\) choices) tonxt(up to \(N\) choices). - Space complexity: \(O(N \cdot 2^{N-1})\)
The DP table size is proportional to the number of states.
Implementation Details
Managing only houses \(2..N\) with bits naturally handles the starting condition (house 1 already visited) and halves the number of states.
In the transition,
rem = full ^ maskis used to extract the unvisited set, then
b = r & -r(lowest set bit) is used for fast enumeration.The DP array is flattened to one dimension as
dp[mask*N + last]for speed and memory optimization.The cost \(w(i,j)\) is precomputed for all pairs (\(O(N^2)\)), so only additions are needed during DP.
Source Code
import sys
from array import array
def main():
input = sys.stdin.readline
N = int(input())
V = list(map(int, input().split()))
if N == 1:
print(0)
return
w = [[0] * N for _ in range(N)]
for i in range(N):
vi = V[i]
ii = i + 1
row = w[i]
for j in range(N):
row[j] = abs(vi - V[j]) * abs(ii - (j + 1))
M = N - 1
full = (1 << M) - 1
INF = 10**18
size = (1 << M) * N
dp = array('Q', [INF]) * size
dp[0] = 0 # mask=0, last=0
for mask in range(1 << M):
base = mask * N
rem = full ^ mask
if mask == 0:
cur = dp[0]
if cur != INF:
r = rem
w0 = w[0]
while r:
b = r & -r
nxt = (b.bit_length() - 1) + 1
newmask = b
idx = newmask * N + nxt
val = cur + w0[nxt]
if val < dp[idx]:
dp[idx] = val
r -= b
continue
tmp = mask
while tmp:
b_last = tmp & -tmp
last = (b_last.bit_length() - 1) + 1
cur = dp[base + last]
if cur != INF and rem:
wl = w[last]
r = rem
while r:
b = r & -r
nxt = (b.bit_length() - 1) + 1
newmask = mask | b
idx = newmask * N + nxt
val = cur + wl[nxt]
if val < dp[idx]:
dp[idx] = val
r -= b
tmp -= b_last
base = full * N
ans = INF
tmp = full
while tmp:
b = tmp & -tmp
last = (b.bit_length() - 1) + 1
val = dp[base + last]
if val < ans:
ans = val
tmp -= b
print(ans)
if __name__ == "__main__":
main()
This editorial was generated by gpt-5.2-high.
投稿日時:
最終更新: