公式

E - 冒険者の旅路 / The Adventurer's Journey 解説 by admin

DeepSeek V3

概要

町と道からなるグラフ上で、各町を初めて訪れると体力が回復する条件下で、町1から町Nに移動する際の到着時の体力の最大値を求める問題です。

考察

この問題の重要なポイントは、同じ町を複数回訪れても回復は最初の1回のみという制約です。つまり、どの町を訪れたか(回復済みかどうか)が状態として重要になります。また、体力は回復によりいくらでも大きくなり得るため、単純なダイクストラ法ではうまく扱えません(負の辺がないが上限がないため)。

状態として「現在の町」と「これまでに訪れた町の集合(回復済みの町の集合)」の2つを組み合わせることで、問題を効率的に解くことができます。各状態での最大体力を記録しながら更新していくことで、最適解を見つけることが可能です。

アルゴリズム

ビットDP(動的計画法)を用います。状態を \((used, v)\) と表現します: - \(used\): これまでに訪れた(回復済みの)町の集合をビットマスクで表現(\(N\)ビット) - \(v\): 現在いる町(0-indexed)

各状態 \((used, v)\) に対して、その状態に到達した時点での最大体力を \(dp[used][v]\) に記録します。

初期状態は \((1<<0, 0)\) で、初期体力は \(F + R_0\) です。その後、以下のように状態を更新します: 1. 現在の状態 \((used, v)\) から、\(v\) に接続する各道 \((u, w)\) について 2. 現在の体力が \(w\) 以上なら移動可能 3. 移動後の体力は \(dp[used][v] - w\) 4. 移動先 \(u\) が未訪問(\(used\) に含まれない)なら、回復 \(R_u\) を加算 5. 新しい状態 \((new\_used, u)\) での体力がこれまでの記録より大きければ更新

これを更新がなくなるまで繰り返し、最終的に状態 \((used, N-1)\) での最大体力を答えとします。

計算量

  • 時間計算量: \(O(2^N \cdot N \cdot M)\)
  • 空間計算量: \(O(2^N \cdot N)\)

\(N \leq 10\) なので、\(2^{10} = 1024\) 状態であり、十分現実的な計算量です。

実装のポイント

  • 状態数は \(2^N \times N\) で、各状態の初期値を十分小さい値(\(-10^{18}\))で初期化

  • 初期状態 \((1<<0, 0)\) の体力を \(F + R[0]\) で設定

  • 更新がなくなるまでループを回す(ベルマンフォード法のような更新方法)

  • 各辺について双方向に移動可能なため、無向グラフとして隣接リストを構築

  • 最終的に全ての状態における町 \(N-1\) の最大体力を確認

    ソースコード

def main():
    import sys
    data = sys.stdin.read().split()
    if not data:
        print(-1)
        return
        
    it = iter(data)
    N = int(next(it)); M = int(next(it)); F_val = int(next(it))
    R = [int(next(it)) for _ in range(N)]
    edges = [[] for _ in range(N)]
    for i in range(M):
        u = int(next(it)) - 1
        v = int(next(it)) - 1
        w = int(next(it))
        edges[u].append((v, w))
        edges[v].append((u, w))
        
    total_states = 1 << N
    INF = -10**18
    dp = [[INF] * N for _ in range(total_states)]
    start_state = 1 << 0
    dp[start_state][0] = F_val + R[0]
    
    updated = True
    while updated:
        updated = False
        for used in range(total_states):
            for v in range(N):
                if dp[used][v] == INF:
                    continue
                for (u, w) in edges[v]:
                    if dp[used][v] < w:
                        continue
                    new_energy = dp[used][v] - w
                    new_used = used
                    if not (used & (1 << u)):
                        new_energy += R[u]
                        new_used |= (1 << u)
                    if new_energy > dp[new_used][u]:
                        dp[new_used][u] = new_energy
                        updated = True
                        
    ans = INF
    for used in range(total_states):
        if dp[used][N-1] > ans:
            ans = dp[used][N-1]
            
    if ans == INF:
        print(-1)
    else:
        print(ans)
        
if __name__ == "__main__":
    main()

この解説は deepseekv3 によって生成されました。

投稿日時:
最終更新: