E - 除雪車の巡回 / Snow Plow Routing Editorial by admin
GPT 5.2 HighOverview
This is a problem of finding the shortest time to traverse all roads (edges) at least once and return to intersection 1. It is a classic form of the Chinese Postman Problem on an undirected graph (the version where the number of odd-degree vertices is small).
Analysis
Key Observations
- The minimum cost of “a closed walk that traverses every edge at least once” is essentially:
- The cost of traversing each edge exactly once: \(S=\sum W_i\)
- Plus the cost of “traversing some edges additional times”
- In an undirected graph, if every vertex has even degree, there exists an Eulerian circuit that traverses each edge exactly once and returns to the starting point.
In this case, no additional cost is needed, so the minimum cost is \(S\).
Why Additional Traversals Are Needed (Odd-Degree Vertices)
- If there are vertices with odd degree, no Eulerian circuit exists.
- To create an Eulerian circuit, we need to “traverse some edges multiple times (equivalent to adding duplicate edges)” so that every vertex has even degree.
- In an undirected graph, the number of odd-degree vertices is always even.
By additionally duplicating paths that pair up these vertices, the degree of both endpoints of each pair increases by 1, making them even.
Why a Naive Approach Fails
- Directly searching over “how many times to traverse each road” and “in what order” causes a combinatorial explosion, making it infeasible for \(N,M\le 10^5\).
- However, in this problem there is a strong constraint that the number of odd-degree vertices is at most 16, which allows us to handle only this small part efficiently.
How to Solve It
- The only paths we need to duplicate are “paths connecting pairs of odd-degree vertices.”
- Finding which pairing minimizes the cost reduces to a minimum weight perfect matching on the set of odd-degree vertices (since \(k\le 16\), this can be solved with bitmask DP).
- The cost of each pair is the “shortest distance on the original graph,” so we run Dijkstra from each odd-degree vertex.
Algorithm
- Build the graph from input, compute each vertex’s degree
deg[v]and the total edge weight sum \(S=\sum W_i\). - Enumerate odd-degree vertices into
odd(let \(k\) be the count).- If \(k=0\), an Eulerian circuit exists and the answer is \(S\).
- Run Dijkstra’s algorithm from each odd-degree vertex \(odd[i]\) to obtain the shortest distances
dist_odd[i][j]to all other odd-degree vertices \(odd[j]\).- In the code, a small optimization is applied by terminating early once distances to all odd-degree vertices are determined.
- Find the minimum cost pairing of odd-degree vertices using bitmask DP.
dp[mask]: the minimum additional cost when the odd-degree vertices included inmaskhave already been paired.- Transition: pick the smallest index
inot yet used inmask, and pair it with another unusedj: $\( dp[mask \cup \{i,j\}] = \min\left(dp[mask \cup \{i,j\}],\ dp[mask] + dist\_odd[i][j]\right) \)$
- The final answer is $\( S + dp[(1<<k)-1] \)$ (add the minimum cost of the additional duplicate traversals).
Complexity
- Time complexity:
- Dijkstra \(k\) times: \(O\left(k \, M \log N\right)\)
- Bitmask DP: \(O\left(2^k \, k^2\right)\) (sufficiently small since \(k\le 16\))
Overall: \(O\left(k M \log N + 2^k k^2\right)\)
- Dijkstra \(k\) times: \(O\left(k \, M \log N\right)\)
- Space complexity:
- Graph: \(O(N+M)\)
- Distance matrix: \(O(k^2)\), DP: \(O(2^k)\)
Overall: \(O(N+M+2^k)\)
- Graph: \(O(N+M)\)
Implementation Notes
Multiple edges are handled correctly by simply adding them to the adjacency list as-is (degree is incremented per edge as well).
In Dijkstra’s algorithm, distances to non-odd-degree vertices are ultimately unnecessary, so
terminating early once “distances to all odd-degree vertices are determined” provides a speedup (theremvariable in the code).In the bitmask DP, by “fixing the smallest unused index
iand searching for its partnerj,” we avoid redundantly computing the same pairings.Source Code
import sys
import heapq
def main():
it = iter(map(int, sys.stdin.buffer.read().split()))
N = next(it)
M = next(it)
g = [[] for _ in range(N + 1)]
deg = [0] * (N + 1)
sum_w = 0
for _ in range(M):
u = next(it)
v = next(it)
w = next(it)
g[u].append((v, w))
g[v].append((u, w))
deg[u] += 1
deg[v] += 1
sum_w += w
odd = [i for i in range(1, N + 1) if deg[i] & 1]
k = len(odd)
if k == 0:
print(sum_w)
return
idx_of = [-1] * (N + 1)
for i, v in enumerate(odd):
idx_of[v] = i
INF = 10**30
heappush = heapq.heappush
heappop = heapq.heappop
def dijkstra(s):
dist = [INF] * (N + 1)
dist[s] = 0
pq = [(0, s)]
res = [INF] * k
rem = k
while pq and rem:
d, u = heappop(pq)
if d != dist[u]:
continue
idx = idx_of[u]
if idx != -1 and res[idx] == INF:
res[idx] = d
rem -= 1
if rem == 0:
break
for v, w in g[u]:
nd = d + w
if nd < dist[v]:
dist[v] = nd
heappush(pq, (nd, v))
return res
dist_odd = [[0] * k for _ in range(k)]
for i, s in enumerate(odd):
d = dijkstra(s)
dist_odd[i] = d
full = (1 << k) - 1
dp = [INF] * (1 << k)
dp[0] = 0
for mask in range(1 << k):
cur = dp[mask]
if cur == INF or mask == full:
continue
i = 0
while (mask >> i) & 1:
i += 1
bit_i = 1 << i
for j in range(i + 1, k):
if (mask >> j) & 1:
continue
nmask = mask | bit_i | (1 << j)
val = cur + dist_odd[i][j]
if val < dp[nmask]:
dp[nmask] = val
print(sum_w + dp[full])
if __name__ == "__main__":
main()
This editorial was generated by gpt-5.2-high.