Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonで最初のノードから最後のノードまでの制限付きパスの数を求めるプログラム


無向の重み付き連結グラフがあるとします。グラフは n 個のノードを持ち、それぞれのノードには 1 から n までのラベルが付けられています。始点から終点へのパスとは [z0, z1, z2, ..., zk] のようなノードの列のことで、z0 が始点ノード、zk が終点ノードであり、隣り合うノード zi と zi+1 の間(0 ≤ i ≤ k-1)には必ず辺が存在します。パスの距離は、そのパスが通る辺の重みの総和として定義されます。また、dist(x) は「ノード n からノード x までの最短距離」を表すものとします。制限付きパス(restricted path)とは、すべての i(0 ≤ i ≤ k-1)について dist(zi) > dist(zi+1) という条件を満たす特別なパスのことです。ここでの課題は、ノード 1 からノード n までの制限付きパスの本数を求めることです。答えが非常に大きくなる可能性があるため、10^9 + 7 で割った余りを返してください。

例として、次のようなグラフが入力された場合を考えます。

Pythonで最初のノードから最後のノードまでの制限付きパスの数を求めるプログラム

このとき出力は 3 になります。制限付きパスは (1,2,5)、(1,2,3,5)、(1,3,5) の 3 本が存在するためです。

解き方のアプローチ

この問題は、ダイクストラ法動的計画法(DP)を組み合わせることで効率的に解けます。以下の手順に従います。

  • 与えられた辺リストをもとに、グラフの隣接リストを作成する
  • サイズ (n+1) の配列 paths を用意し、すべて 0 で初期化する
  • paths[n] := 1 と設定する(終点自身へのパスは 1 通り)
  • サイズ (n+1) の配列 dists を用意し、すべて -1 で初期化する(未確定の印)
  • 優先度付きキュー q を用意し、最初に (0, n) を挿入する
  • q が空になるまで、次の処理を繰り返す
    • (dist, node) := キューの先頭要素を取り出す
    • dists[node] が -1 以外(すでに確定済み)なら、次の反復へスキップする
    • dists[node] := dist として距離を確定させる
    • node に隣接する各ノード v と辺の重み w について、
      • dists[v] が -1 の場合:(dist + w, v) をキューに挿入する
      • dists[v] < dists[node] の場合:paths[node] := paths[node] + paths[v] として経路数を累積する
    • node が 1 と等しければ、paths[node] mod (10^9 + 7) を返す
  • ループが終了したら 0 を返す

なぜこのアルゴリズムが機能するのか

終点であるノード n を起点にダイクストラ法を実行すると、ノードは「ノード n からの距離が近い順」に確定していきます。したがって、あるノードが確定した時点で、そのノードより dist が小さい隣接ノードは必ず先に確定済みになっており、paths[v] の値はすでに最終的な値になっています。この性質により、「dist(zi) > dist(zi+1)」という制限を満たすパスの数を、paths[node] = Σ paths[v](v は dist がより小さい隣接ノード)という漸化式で正しく数え上げることができます。計算量は O(E log V) です。

実装例

理解を深めるために、以下の Python 実装を見てみましょう。

from collections import defaultdict
from heapq import heappop, heappush


def solve(n, edges):
    graph = defaultdict(dict)
    for u, v, w in edges:
        graph[u][v] = w
        graph[v][u] = w

    paths = [0] * (n + 1)
    paths[n] = 1
    dists = [-1] * (n + 1)
    q = [(0, n)]

    while q:
        dist, node = heappop(q)
        if dists[node] != -1:
            continue

        dists[node] = dist
        for v, w in graph[node].items():
            if dists[v] == -1:
                heappush(q, (dist + w, v))
            elif dists[v] < dists[node]:
                paths[node] += paths[v]

        if node == 1:
            return paths[node] % (10**9 + 7)

    return 0


n = 5
edges = [(1,2,3),(1,3,3),(2,3,1),(1,4,2),(5,2,2),(3,5,1),(5,4,10)]
print(solve(n, edges))

入力

5,[(1,2,3),(1,3,3),(2,3,1),(1,4,2),(5,2,2),(3,5,1),(5,4,10)]

出力

3

  1. Pythonでリスト内の最大値を見つける方法|sort()とmax()の2つのアプローチ

    この記事では、リストの中から最大の数値を見つけるための解決策とアプローチについて詳しく解説します。問題の概要数値のリストが与えられたとき、その中から最大の要素を見つけ出す必要があります。Pythonでは、主に以下の2つの方法でこれを実現できます。ソート(並べ替え)を利用する方法組み込み関数 max() を利用する方法アプローチ1:sort() 関数を使う方法リストを sort() メソッドで昇順に並べ替えると、リストの最後の要素(インデックス -1)が必ず最大値になります。サンプルコードlist1 = [18, 65, 78, 89, 90] list1.sort() # メイン処理 prin

  2. 【Python入門】文字列の中から最初の繰り返しのない文字を見つける2つの方法

    この記事では、文字列や文字のストリームの中から最初に現れる繰り返しのない文字(ユニークな文字)を見つける方法を解説します。この問題には複数のアプローチがあり、本稿では同じ文字列に対して2つの異なるプログラムを作成して比較してみます。方法1:関数と辞書を使う方法(O(n)アルゴリズム)まずは、辞書(dict)を使って各文字の出現回数をカウントし、出現順序も保持する効率的な関数ベースの方法です。def firstNonRepeatingChar(str1): char_order = [] counts = {} for c in str1: if c in