公式

E - 除雪車の巡回 / Snow Plow Routing 解説 by admin 印刷ページ

Gemini 3.0 Flash (Thinking)

Overview

This problem asks for the shortest route that traverses all edges of a graph at least once and returns to the starting point. In graph theory, this is known as the “Chinese Postman Problem (Route Inspection Problem)”.

Analysis

1. Relationship Between Euler Circuits and Vertex Degrees

The condition for a route that traverses every edge of a graph exactly once and returns to the start (an Euler circuit) to exist is that “every vertex has even degree (even number of incident edges)”.

If all vertices have even degree, then the answer is simply the sum of all edge weights. However, if there are vertices with odd degree (odd vertices), an Euler circuit cannot be constructed as-is.

2. Resolving Odd Vertices

When odd vertices exist, we need to “traverse some edges twice” to make all apparent degrees even. - Traversing an edge twice is equivalent to “adding a new edge with the same weight” between those two vertices. - The total number of odd vertices in any graph is always even (Handshaking Lemma). - By pairing up the odd vertices two at a time and connecting each pair via the shortest path (= traversing all edges on that path twice), the degree of each paired vertex increases by 1, becoming even.

Therefore, this problem reduces to “how should we pair up the odd vertices to minimize the total length of the added paths?”

3. Utilizing the Constraints

The problem statement contains a very important constraint: “the number of vertices with odd degree is at most 16.” Letting the number of odd vertices be \(K\), we have \(K \le 16\). By leveraging this small size, it is possible to find the optimal pairing using dynamic programming (DP).

Algorithm

1. Compute the Total Weight of All Edges

First, calculate the sum of all edge weights as the base cost for traversing every edge once.

2. Find Shortest Distances Between Odd Vertices

For all pairs among the \(K\) odd vertices, find the shortest distance between them. - Run Dijkstra’s algorithm \(K\) times, once from each odd vertex as the source. - This creates a \(K \times K\) distance matrix dist_matrix.

3. Minimum Weight Perfect Matching via Bitmask DP

Let \(S\) be the set of odd vertices. We find the minimum cost of pairing them using bitmask DP. - dp[mask]: the minimum additional cost when the set of already paired vertices is represented by mask (a bitmask). - Transition: 1. Choose one vertex \(i\) that has not been used yet. 2. Try all other unused vertices \(j\) as the partner for \(i\). 3. dp[mask | (1<<i) | (1<<j)] = min(..., dp[mask] + dist_matrix[i][j])

4. Final Answer

The answer is total sum of all edge weights + dp[(1 << K) - 1].

Complexity

Let the number of odd vertices be \(K\) (\(K \le 16\)), the number of vertices be \(N\), and the number of edges be \(M\).

  • Time Complexity: \(O(K(M \log N) + 2^K \cdot K)\)

    • Running Dijkstra’s algorithm \(K\) times: \(O(K \cdot M \log N)\)
    • Bitmask DP: With \(2^K\) states and trying \(K\) options per transition, this is \(O(2^K \cdot K)\)
    • Since \(K \le 16\), we have \(2^{16} = 65536\), which is well within time limits.
  • Space Complexity: \(O(N + M + 2^K)\)

    • Memory required for the adjacency list, distance arrays, and DP table.

Implementation Notes

  • Speeding up the DP: For each mask, it suffices to fix the smallest-numbered unused vertex and only search for its partner. This avoids exploring duplicate combinations.

  • Early termination of Dijkstra’s algorithm: During Dijkstra’s execution, if the shortest distances to all odd vertices have been determined, the search can be terminated early to reduce runtime.

  • Handling multiple edges: As long as all edges are stored in the adjacency list, Dijkstra’s algorithm works as usual.

    Source Code

import sys
from heapq import heappush, heappop

def solve():
    # Fast input reading
    try:
        input_data = sys.stdin.read().split()
    except EOFError:
        return
        
    if not input_data:
        return
    
    # Map input data to integers
    data = list(map(int, input_data))
    N, M = data[0], data[1]
    
    # Adjacency list and degree count
    adj = [[] for _ in range(N + 1)]
    total_weight = 0
    degree = [0] * (N + 1)
    
    ptr = 2
    for _ in range(M):
        u, v, w = data[ptr], data[ptr+1], data[ptr+2]
        ptr += 3
        adj[u].append((v, w))
        adj[v].append((u, w))
        degree[u] += 1
        degree[v] += 1
        total_weight += w
        
    # Identify vertices with odd degrees
    odd_vertices = [i for i in range(1, N + 1) if degree[i] % 2 != 0]
    num_odd = len(odd_vertices)
    
    # If all degrees are even, an Eulerian circuit exists
    if num_odd == 0:
        sys.stdout.write(str(total_weight) + '\n')
        return

    # Map odd vertices for faster lookup
    is_odd = [False] * (N + 1)
    for v in odd_vertices:
        is_odd[v] = True
        
    # Pre-calculate shortest path distances between all pairs of odd vertices
    # num_odd is guaranteed to be even and <= 16
    dist_matrix = [[0] * num_odd for _ in range(num_odd)]
    heappush_func = heappush
    heappop_func = heappop
    INF = 10**18
    
    for i in range(num_odd - 1):
        start_node = odd_vertices[i]
        dists = [INF] * (N + 1)
        dists[start_node] = 0
        pq = [(0, start_node)]
        
        found_count = 0
        while pq:
            d, u = heappop_func(pq)
            if d > dists[u]:
                continue
            if is_odd[u]:
                found_count += 1
                # Dijkstra pops nodes in increasing order of distance.
                # Once we've popped all odd vertices, we have their shortest distances.
                if found_count == num_odd:
                    break
            
            # Optimization: use local reference for adj[u]
            adj_u = adj[u]
            for v, w in adj_u:
                new_dist = d + w
                if dists[v] > new_dist:
                    dists[v] = new_dist
                    heappush_func(pq, (new_dist, v))
        
        # Fill the distance matrix for all pairs involving odd_vertices[i]
        dist_i = dist_matrix[i]
        for j in range(i + 1, num_odd):
            dist_i[j] = dist_matrix[j][i] = dists[odd_vertices[j]]
    
    # Solve minimum weight perfect matching on odd vertices using bitmask DP
    full_mask = (1 << num_odd) - 1
    dp = [INF] * (1 << num_odd)
    dp[0] = 0
    
    for mask in range(full_mask):
        if dp[mask] == INF:
            continue
        
        # Pick the first vertex i not in the current matching mask
        i = 0
        while (mask >> i) & 1:
            i += 1
            
        mask_with_i = mask | (1 << i)
        dist_i = dist_matrix[i]
        dp_mask = dp[mask]
        
        # Try pairing vertex i with every other vertex j not in the mask
        for j in range(i + 1, num_odd):
            if not ((mask >> j) & 1):
                new_mask = mask_with_i | (1 << j)
                cost = dp_mask + dist_i[j]
                if dp[new_mask] > cost:
                    dp[new_mask] = cost
                    
    # The answer is the original total weight plus the cost of added paths
    sys.stdout.write(str(total_weight + dp[full_mask]) + '\n')

if __name__ == '__main__':
    solve()

This editorial was generated by gemini-3-flash-thinking.

投稿日時:
最終更新: