Pythonで理解する蟻本「2-5 Layout(POJ No.3169)」(p.104)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 Layout(POJ No.3169)」(p.104)
のコードをPythonで書き直したものとなっています。
入力
入力例
4 2 1
1 3 10
2 4 20
2 3 3
解答
import sys
MAX_N = 1000
MAX_ML = 10000
MAX_MD = 10000
INF = float('inf')
# 入力
N, ML, MD = map(int,input().split())
AL = [0] * MAX_ML
BL = [0] * MAX_ML
DL = [0] * MAX_ML
for i in range(ML):
AL[i], BL[i], DL[i] = map(int,input().split())
AD = [0] * MAX_MD
BD = [0] * MAX_MD
DD = [0] * MAX_MD
for i in range(MD):
AD[i], BD[i], DD[i] = map(int,input().split())
d = [0] * MAX_N # 最短距離
updated = False # 更新されたか
def update(x, y, i):
global updated
if x > y:
d[i] = y
updated = True
# ベルマンフォード法によりdを計算する
def bellmanford():
global updated
for k in range(N + 1):
updated = False
# i+1からiへコスト0
for i in range(N):
if d[i + 1] < INF:
update(d[i], d[i + 1], i)
# ALからBLへコストDL
for i in range(ML):
if d[AL[i] - 1] < INF:
update(d[BL[i] - 1], d[AL[i] - 1] + DL[i], BL[i] - 1)
# BDからADへコスト-DD
for i in range(MD):
if d[BD[i] - 1] < INF:
update(d[AD[i] - 1], d[BD[i] - 1] - DD[i], AD[i] - 1)
# 負閉路チェック
bellmanford()
if updated:
print(-1)
sys.exit() # ここでプログラムを終了
d = [INF] * MAX_N
d[0] = 0
bellmanford()
res = d[N - 1]
if res == INF:
res = -2
print(res)
Pythonで理解する蟻本「2-5 Conscription(POJ No.3723)」(p.103)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 Conscription(POJ No.3723)」(p.103)
のコードをPythonで書き直したものとなっています。
入力
入力例
5 5 8
4 3 6831
1 3 4583
0 0 6592
0 1 3063
3 3 4975
1 3 2049
4 2 2104
2 2 781
解答
# クラスカル法のコード
class UnionFindTree():
# n要素で初期化
def __init__(self, n):
self.n = n
self.par = list(range(n))
self.rank = [0] * n
# 木の根を求める
def find(self, x):
if self.par[x] == x:
return x
else:
self.par[x] = self.find(self.par[x])
return self.par[x]
# xとyの属する集合を併合
def unite(self, x, y):
x = self.find(x)
y = self.find(y)
if x == y:
return
if self.rank[x] < self.rank[y]:
self.par[x] = y
else:
self.par[y] = x
if self.rank[x] == self.rank[y]:
self.rank[x] += 1
# xとyが同じ集合に属するか否か
def same(self, x, y):
return self.find(x) == self.find(y)
def kruskal():
es.sort() # 辺のコストが小さい順にソートする
UFT = UnionFindTree(V) # Union-Findの初期化
res = 0
for i in range(E):
e = es[i]
if not UFT.same(e[1], e[2]):
UFT.unite(e[1], e[2])
res += e[0]
return res
# ここから蟻本のコード
MAX_R = 50000
# 入力
N, M, R = map(int,input().split())
x = [0] * MAX_R
y = [0] * MAX_R
d = [0] * MAX_R
for i in range(R):
x[i], y[i], d[i] = map(int,input().split())
V = N + M
E = R
es = []
for i in range(R):
es.append([-d[i], N + x[i], y[i]])
print(10000 * (N + M) + kruskal())
Pythonで理解する蟻本「2-5 Roadblocks(POJ No.3255)」(p.102)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 Roadblocks(POJ No.3255)」(p.102)
のコードをPythonで書き直したものとなっています。
入力
入力例
4 4
1 2 100
2 3 250
2 4 200
3 4 100
解答
# 優先度付きキューをインポートする
import heapq
MAX_N = 5000
MAX_R = 100000
INF = float('inf')
# 入力
N, R = map(int,input().split())
G = [[] for _ in range(MAX_N)] # グラフの隣接リスト表現
for i in range(R):
s, t, c = map(int,input().split())
G[s-1].append([t-1, c])
G[t-1].append([s-1, c])
que = []
dist = [INF] * MAX_N # 最短経路
dist2 = [INF] * MAX_N # 二番目の最短経路
dist[0] = 0
heapq.heappush(que, [0, 0])
while len(que) != 0:
p = heapq.heappop(que)
d = p[0]
v = p[1]
if dist2[v] < d:
continue
for i in range(len(G[v])):
e = G[v][i]
d2 = d + e[1]
if dist[e[0]] > d2:
dist[e[0]], d2 = d2, dist[e[0]]
heapq.heappush(que, [dist[e[0]], e[0]])
if dist2[e[0]] > d2 and dist[e[0]] < d2:
dist2[e[0]] = d2
heapq.heappush(que, [dist2[e[0]], e[0]])
print(dist2[N - 1])
Pythonで理解する蟻本「2-5 最小全域木問題2(クラスカル法)」(p.101)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 最小全域木問題2(クラスカル法)」(p.101)
のコードをPythonで書き直したものとなっています。
入力
入力例
7 9
0 1 2
0 4 10
1 2 1
1 3 3
1 5 7
3 5 1
3 6 5
4 5 5
5 6 8
コード
# Union-Find木のコード
class UnionFindTree():
# n要素で初期化
def __init__(self, n):
self.n = n
self.par = list(range(n))
self.rank = [0] * n
# 木の根を求める
def find(self, x):
if self.par[x] == x:
return x
else:
self.par[x] = self.find(self.par[x])
return self.par[x]
# xとyの属する集合を併合
def unite(self, x, y):
x = self.find(x)
y = self.find(y)
if x == y:
return
if self.rank[x] < self.rank[y]:
self.par[x] = y
else:
self.par[y] = x
if self.rank[x] == self.rank[y]:
self.rank[x] += 1
# xとyが同じ集合に属するか否か
def same(self, x, y):
return self.find(x) == self.find(y)
# ここから蟻本のコード
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
es = []
for _ in range(E):
s, t, c = map(int,input().split())
es.append([c, s, t]) # c(辺のコスト)でソートできるように最初の要素にしておく
def kruskal():
es.sort() # 辺のコストが小さい順にソートする
UFT = UnionFindTree(V) # Union-Findの初期化
res = 0
for i in range(E):
e = es[i]
if not UFT.same(e[1], e[2]):
UFT.unite(e[1], e[2])
res += e[0]
return res
res = kruskal()
print(res)
実行結果
17
Pythonで理解する蟻本「2-5 最小全域木問題1(プリム法)」(p.100)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 最小全域木問題1(プリム法)」(p.100)
のコードをPythonで書き直したものとなっています。
入力
入力例
7 9
0 1 2
0 4 10
1 2 1
1 3 3
1 5 7
3 5 1
3 6 5
4 5 5
5 6 8
蟻本に載っているコード(O(|V|^2))
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
cost = [[INF] * V for _ in range(V)] # cost[u][v]は辺e=(u,v)のコスト(存在しない場所はINF)
mincost = [INF] * V # 集合Xからの辺の最終コスト
used = [False] * V # 頂点iがXに含まれているか
for _ in range(E):
s, t, c = map(int,input().split())
cost[s][t] = c
cost[t][s] = c
def prim():
mincost[0] = 0
res = 0
while True:
v = -1
# Xに属さない頂点のうちXからの辺のコストが最小になる頂点を探す
for u in range(V):
if not used[u] and (v == -1 or mincost[u] < mincost[v]):
v = u
if v == -1:
break
used[v] = True # 頂点vをXに追加
res += mincost[v] # 辺のコストを加える
for u in range(V):
mincost[u] = min(mincost[u], cost[v][u])
return res
res = prim()
print(res)
実行結果
17
優先度付きキューを使った解法(O(|E| log |V|))
# 優先度付きキューをインポートする
import heapq
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
cost = [[INF] * V for _ in range(V)] # cost[u][v]は辺e=(u,v)のコスト(存在しない場所はINF)
used = [False] * V # 頂点iがXに含まれているか
for _ in range(E):
s, t, c = map(int,input().split())
cost[s][t] = c
cost[t][s] = c
def prim():
res = 0
q = [[0, 0]]
while True:
v = -1
MIN = INF
# Xに属さない頂点のうちXからの辺のコストが最小になる頂点を探す
while len(q) != 0:
c, u = heapq.heappop(q)
if not used[u] and (v == -1 or c < MIN):
v = u
MIN = c
break
if v == -1:
break
used[v] = True # 頂点vをXに追加
res += c # 辺のコストを加える
for u in range(V):
if cost[v][u] != INF:
heapq.heappush(q, [cost[v][u], u])
return res
res = prim()
print(res)
実行結果
17
Pythonで理解する蟻本「2-5 経路復元」(p.98)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 経路復元」(p.98)
のコードをPythonで書き直したものとなっています。
入力例
頂点0→2→3→5→4→6が最短経路となる。
7 10
0 1 2
0 2 5
1 2 4
1 3 6
1 4 10
2 3 2
3 5 1
4 5 3
4 6 5
5 6 9
隣接リストを用いたコード(O(|V|^2))
INF = float('inf')
prev = []
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
cost = [[INF] * V for _ in range(V)] # cost[u][v]は辺e=(u,v)のコスト(存在しない場所はINF)
for _ in range(E):
s, t, c = map(int,input().split())
cost[s][t] = c
cost[t][s] = c
d = [INF for _ in range(V)] # 最短距離
used = [False] * V # すでに使われたかのフラグ
prev = [-1] * V # 最短路の直前の頂点
# 始点sから各頂点への最短距離を求める
def dijkstra(s):
d[s] = 0
while True:
v = -1
for u in range(V):
if not used[u] and (v == -1 or d[u] < d[v]):
v = u
if v == -1:
break
used[v] = True
for u in range(V):
if d[u] > d[v] + cost[v][u]:
d[u] = d[v] + cost[v][u]
prev[u] = v
# 頂点tへの最短路
def get_path(t):
path = []
while t != -1:
path.append(t) # tがsになるまでprev[t]を辿っていく
t = prev[t]
# このままだとt->sの順になっているので逆順にする
path.reverse()
return path
dijkstra(0) # 頂点0を始点とする
path = get_path(6) # 頂点0から頂点6への最短経路
print(path)
実行結果
[0, 2, 3, 5, 4, 6]
Pythonで理解する蟻本「2-5 全点対最短路問題(ワーシャル-フロイド法)」(p.98)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 全点対最短路問題(ワーシャル-フロイド法)」(p.98)
のコードをPythonで書き直したものとなっています。
入力
蟻本には負の閉路がないグラフだけが入力例として載っていたので、負の閉路があるグラフも入力例として載せておきます。
また、蟻本に載っているグラフは無向グラフですが、このブログで追加した負の閉路があるグラフの入力例では有向グラフを使っています。
そのため、この記事では入力例は有向グラフに統一してあります。
入力例1
上記のコードは実際に実行すると のように一行で表示されます。 しかし、この形式はとても見にくいため下の実行例では というように改行しています。 実際の実行結果とは異なりますが、リストの中身自体は同じなので問題ありません。 これは上の実行例でも確かに成り立っています。
7 20
0 1 2
1 0 2
0 2 5
2 0 5
1 2 4
2 1 4
1 3 6
3 1 6
1 4 10
4 1 10
2 3 2
3 2 2
3 5 1
5 3 1
4 5 3
5 4 3
4 6 5
6 4 5
5 6 9
6 5 9
入力例2
6 7
0 1 2
0 3 4
1 2 3
2 3 5
2 5 2
4 2 -4
5 4 1
コード
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
d = [[INF] * V for _ in range(V)] # d[u][v]は辺e=(u,v)のコスト(存在しない場合はINF、ただしd[i][i]=0とする)
for i in range(V):
d[i][i] = 0
for _ in range(E):
f, t, c = map(int,input().split())
d[f][t] = c
def warshall_floyd():
for k in range(V):
for i in range(V):
for j in range(V):
d[i][j] = min(d[i][j], d[i][k] + d[k][j])
warshall_floyd()
print(d)
実行結果
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]
[[0, 1, 2],
[3, 4, 5],
[6, 7, 8]]
入力例1の場合
[[0, 2, 5, 7, 11, 8, 16],
[2, 0, 4, 6, 10, 7, 15],
[5, 4, 0, 2, 6, 3, 11],
[7, 6, 2, 0, 4, 1, 9],
[11, 10, 6, 4, 0, 3, 5],
[8, 7, 3, 1, 3, 0, 8],
[16, 15, 11, 9, 5, 8, 0]]
入力例2の場合
[[0, 2, 4, 4, 8, 6],
[inf, 0, 2, 7, 6, 4],
[inf, inf, -1, 4, 3, 1],
[inf, inf, inf, 0, inf, inf],
[inf, inf, -5, 0, -1, -3],
[inf, inf, -4, 1, 0, -2]]
頂点iから頂点jに到達できない場合はd[i][j] = infとなり、負の閉路が存在する場合はd[i][i]が負となる頂点iが存在します。