公式

D - もこもこクラウドホッピング / Fluffy Cloud Hopping 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

This is a shortest path problem on a graph consisting of \(N\) clouds and \(M\) wind paths, where edge costs change depending on the “current fluffiness level.” We exploit the fact that the number of fluffiness types \(K\) is at most \(5\), and solve it using Dijkstra’s algorithm with an extended state space.

Analysis

Difference from ordinary shortest path

In standard Dijkstra’s algorithm, it suffices to manage only the shortest distance to each node. However, in this problem, even if you arrive at the same cloud, if the “current fluffiness level” differs, the subsequent movement costs will change.

Example: If you arrive at cloud \(A\) with cost \(10\) and fluffiness \(1\), versus arriving with cost \(12\) and fluffiness \(1\), the former is clearly better. However, if you arrive with cost \(10\) and fluffiness \(5\), versus cost \(12\) and fluffiness \(1\), the latter may ultimately be advantageous because subsequent movement costs become smaller.

In other words, we need to include not just the node but also the “current fluffiness level” as part of the state.

Exploiting the small \(K\)

The fluffiness level is an integer between \(1\) and \(K\) inclusive, with \(K \leq 5\). Therefore, even if we define the state as \((\text{cloud number}, \text{current fluffiness level})\), the number of states is \(N \times K\), which is at most around \(500{,}000\), allowing sufficiently fast processing.

Algorithm

We use state-extended Dijkstra’s algorithm.

  1. State definition: \((u, f)\) = “currently at cloud \(u\) with fluffiness level \(f\)”
  2. Initial state: \((1, F_1)\) with cost \(0\)
  3. Transition: From state \((u, f)\), traversing edge \((u, v, T)\) costs \(T \times f\) to arrive at cloud \(v\). After arrival, we can transition to two states:
    • \((v, f)\): maintain the current fluffiness level
    • \((v, F_v)\): adopt the fluffiness level of the arrived cloud
  4. Answer: \(\min_{f=1}^{K} \text{dist}(N, f)\)

This can be solved within the same framework as standard Dijkstra’s algorithm, as a shortest path problem on a weighted graph with \(N \times K\) nodes.

Concrete Example

In a graph with \(K = 3\), when traveling cloud \(A\) (fluffiness \(3\)) → cloud \(B\) (fluffiness \(1\)) → cloud \(C\): - \(A \to B\): fluffiness is \(3\), so cost is \(T_{AB} \times 3\) - After arriving at \(B\), change fluffiness to \(1\) - \(B \to C\): fluffiness is \(1\), so cost is \(T_{BC} \times 1\)

The strategy of routing through clouds with low fluffiness levels to reduce subsequent movement costs is important.

Complexity

  • Time complexity: \(O(MK \log(NK))\)
    • The number of states is \(O(NK)\), transitions occur from up to \(K\) types of states for each edge, and priority queue operations in Dijkstra’s algorithm cost \(\log\).
  • Space complexity: \(O(NK + M)\)
    • \(O(NK)\) for the distance array, \(O(M)\) for the adjacency list.

Implementation Notes

  • State indexing: Managing states with a 1D array like dist[u * K + fi] is fast (fi is \(f - 1\), i.e., \(0\)-indexed).

  • Avoiding duplicate transitions: When the fluffiness \(F_v\) of the arrived cloud equals the current fluffiness \(f\), the “maintain” and “adopt” transitions lead to the same state, so processing only one is sufficient.

  • Fast input in Python: Using sys.stdin.buffer.read() for bulk reading avoids TLE even with large inputs.

  • Output the answer: Since we may arrive at cloud \(N\) with any fluffiness level, we take the minimum of \(\text{dist}(N, 1), \text{dist}(N, 2), \ldots, \text{dist}(N, K)\).

    Source Code

import heapq
import sys

def main():
    data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(data[idx]); idx += 1
    M = int(data[idx]); idx += 1
    K = int(data[idx]); idx += 1
    
    F = [0] * N
    for i in range(N):
        F[i] = int(data[idx]); idx += 1
    
    adj = [[] for _ in range(N)]
    for j in range(M):
        u = int(data[idx]) - 1; idx += 1
        v = int(data[idx]) - 1; idx += 1
        t = int(data[idx]); idx += 1
        adj[u].append((v, t))
        adj[v].append((u, t))
    
    INF = float('inf')
    # dist[node * K + f_index]
    size = N * K
    dist = [INF] * size
    
    start_fi = F[0] - 1
    dist[0 * K + start_fi] = 0
    
    pq = [(0, 0, start_fi)]
    
    while pq:
        d, u, fi = heapq.heappop(pq)
        
        uid = u * K + fi
        if d > dist[uid]:
            continue
        
        f = fi + 1  # actual mokumoku degree
        
        for v, t in adj[u]:
            cost = d + t * f
            vid_keep = v * K + fi
            if cost < dist[vid_keep]:
                dist[vid_keep] = cost
                heapq.heappush(pq, (cost, v, fi))
            
            fv = F[v] - 1
            if fv != fi:
                vid_adopt = v * K + fv
                if cost < dist[vid_adopt]:
                    dist[vid_adopt] = cost
                    heapq.heappush(pq, (cost, v, fv))
            # if fv == fi, already handled above
    
    base = (N - 1) * K
    ans = min(dist[base: base + K])
    if ans == INF:
        print(-1)
    else:
        print(ans)

main()

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