典型90【013 – Passing(★5)】をpython解説

Python

AtCoder の問題を解くために必要な実力を付けるために作られた「典型問題」を解いていく企画として、「典型90」があります。

今回は、その内、の13問目である 013 – Passing(★5)を解説していきたいと思います。
なにか、間違っているところなどありましたら、@EitaSugiまで、ご指導ご鞭撻の程、よろしくお願い致します!

問題名 【013 – Passing(★5)】

013 – Passing(★5)

考察

問題文の読み替え

今回の問題は、

  • 1番目の都市からk番目の都市に行くまでにかかる時間の最小値
  • N番目の都市からk番目の都市に行くまでにかかる時間の最小値(双方向なので、k↔Nは同じコスト)

合計を求める問題です。

今回のように、ある1つの頂点から各頂点までの最短経路を高速で求めるには、ダイクストラ法を用いる方法です。

ここでは、ダイクストラ法の概要だけお話します。ダイクストラ法についての詳しい解説は、こちらの記事はわかりやすくまとまっていますので、ご参照ください。

ダイクストラ法の概要

ダイクストラ法は、ある任意の頂点から各頂点までの最短経路をO((N+M)logN)の計算量で求めることができます。
なので、事前に1番目からのi番目の最短経路N番目からi番目の最短経路を求めておけば、計算時間は間に合います。
(※ダイクストラ法は、閉路やマイナスのコストがあると使えないので注意)

典型解法:任意の頂点からの最短経路はダイクストラ法

コード

今回使用しているダイクストラ法は、最短経路までのパスの数もカウントするダイクストラ法ですが、今回はそれは使わないので、無視して大丈夫です。

import sys
def LI(): return list(map(int,sys.stdin.readline().rstrip().split()))
def II(): return int(sys.stdin.readline())
def MI(): return map(int,sys.stdin.readline().rstrip().split())
def S(): return sys.stdin.readline().rstrip()
readline = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)

# https://atcoder.jp/contests/joi2008yo/tasks/joi2008yo_f
import sys
def LI(): return list(map(int,sys.stdin.readline().rstrip().split()))
def II(): return int(sys.stdin.readline())
def MI(): return map(int,sys.stdin.readline().rstrip().split())
def S(): return sys.stdin.readline().rstrip()

# O((N+M)logN)
from heapq import heappush, heappop
from collections import defaultdict
# 多重辺にも対応
# V=ノード数,E=エッジ数とすると、計算量はO((V+E)logV)
def Dijkstra(adj, start):
    # adjがstartと隣接するnode,startは出発点
    inf = 10**18
    MOD = 10**9+7
    n = len(adj)
    dist = [inf]*n#初期値をinfに固定
    prev = [inf]*n
    # prev[i]:頂点iに至る直前に居た頂点の番号.これを持っておけば,goalからstartまで戻れる
    variety = [0]*(n)  # 最短経路までの道のりは何種類あるか判定
    variety[0] = 1 #0→0は1通りしかないため
    dist[start] = 0
    hq = [(0, start)]
    undetermined = n-1  # 枝刈りするためのフラグ
    while hq:
        d, u = heappop(hq)  # hpから最小値を取り出している
        # 最初は 0,startを取り出している
        # 0からstartまで最短経路を出すため
        if dist[u] < d:  # もし既により最適な経路がある
            continue
        for v, cost in adj[u].items():  # 同じコストの最適な経路が見つかったら
            if dist[v] == (dist[u] + cost):
                variety[v] += variety[u] #uの場所まで行く最短経路のpath分だけ、vに行くまでの最短経路が増える
                variety[v] %= MOD

            elif dist[v] > (dist[u] + cost): # 新しく最短経路が見つかった場合
                dist[v] = dist[u] + cost
                variety[v] = variety[u] #uの場所まで行く最短経路のpath分だけ、vに行くまでの最短経路がある
                heappush(hq, (dist[v], v))  # hp に dist[v]とvをpush(追加)している
                # hpに格納しているのは、移動コスト(dist[v])とその場所
                prev[v] = u  # prev[v](現在地)はuという場所からきたことを示している
        undetermined -= 1
        if undetermined == 0:
            # 全点確定後の枝刈り(非連結なら意味はない)
            # 枝刈り探索法の原理
            # 与えられた問題を解くのに,構成要素を1つずつ吟味し,
            # 問題の解決に不必要だと思われるものを冗長なものとして
            # 削除し,縮小された問題を再帰的に解く.
            break
    return dist, variety
    #distはスタートからの各nodeまでの最小コストを出力
    #varietyは最小コストで行く方法は何通りあるかを出力

N,M = MI()
dd = {}
inf = 10**18
G = [defaultdict(int) for i in range(N)]
for i in range(M):
    a,b,c = MI()
    a -= 1 #indexを0始まりにする
    b -= 1 #indexを0始まりにする
    G[a][b] = c
    G[b][a] = c

dist1,v= Dijkstra(G,0) #0番目の頂点から各頂点までの最短経路 indexは0始まりのため、1ではなく0
distN,v = Dijkstra(G,N-1) #N-1番目の頂点から各頂点までの最短経路indexは0始まりのため、NではなくN-1

for i in range(N):
    print(dist1[i]+distN[i])

まとめ

ダイクストラ法とか、UnionFindとかクラスカル法とか、グラフの問題は覚えなきゃいけない展開解法多すぎ

前の問題【012 – Red Painting(★4)】
次の問題【014 – We Used to Sing a Song Together(★3)】

コメント

タイトルとURLをコピーしました