問題
$N$ 頂点 $M$ 辺の有向グラフが与えられる。各辺 $(u, v)$ には信頼度 $p_{uv} \in (0,1]$(有理数として $a/b$ で与えられる)が付いている。
頂点 $s$ から頂点 $t$ へのパスの信頼度は辺の信頼度の積で定義される。$s$ から $t$ への最大信頼度パスの値を $\bmod 10^9+7$ で出力せよ(分数のまま modular inverse を使って計算)。
$Q$ 個のクエリ $(s_i, t_i)$ に対してそれぞれ答えよ。
制約
| パラメータ | 範囲 |
|---|---|
| $N$ | $2 \le N \le 300$ |
| $M$ | $1 \le M \le 5000$ |
| $Q$ | $1 \le Q \le 10^5$ |
| $a_i, b_i$ | $1 \le a_i \le b_i \le 10^9$ |
入出力例
入力例 1
3 3 2
1 2 3 4
2 3 2 3
1 3 1 2
1 3
2 3
出力例 1
500000004
666666672
1→2→3: $\frac{3}{4} \times \frac{2}{3} = \frac{1}{2}$, 1→3: $\frac{1}{2}$(同値)。2→3: $\frac{2}{3}$。 $\frac{1}{2} \bmod 10^9+7 = 500000004$, $\frac{2}{3} \bmod 10^9+7 = 666666672$。
概念図: max-times 半環上の Floyd-Warshall
ヒント(段階的開示)
ヒント1: 方向性
ヒント2: アプローチ
- 辺の信頼度 $a/b$ の対数 $\log(a/b)$ を float で保持(比較用)
- mod値 $a \cdot b^{-1} \bmod p$ を別途管理(出力用)
- Floyd-Warshall で $\max$ 方向に更新:$\text{dist}\_f[i][j] = \max(\text{dist}\_f[i][j], \text{dist}\_f[i][k] + \text{dist}\_f[k][j])$
ヒント3: コード骨格
for k in range(N):
for i in range(N):
if dist_f[i][k] == NEG_INF: continue
for j in range(N):
if dist_f[k][j] == NEG_INF: continue
cand = dist_f[i][k] + dist_f[k][j] # log の和 = 積の log
if cand > dist_f[i][j]:
dist_f[i][j] = cand
dist_m[i][j] = dist_m[i][k] * dist_m[k][j] % MOD
模範解答 (Python)
import sys, math
input = sys.stdin.readline
MOD = 10**9 + 7
def modinv(a):
return pow(a, MOD - 2, MOD)
def solve():
N, M, Q = map(int, input().split())
NEG_INF = -math.inf
dist_f = [[NEG_INF] * N for _ in range(N)]
dist_m = [[0] * N for _ in range(N)]
for i in range(N):
dist_f[i][i] = 0.0
dist_m[i][i] = 1
for _ in range(M):
u, v, a, b = map(int, input().split())
u -= 1; v -= 1
log_w = math.log(a) - math.log(b)
mod_w = a * modinv(b) % MOD
if log_w > dist_f[u][v]:
dist_f[u][v] = log_w
dist_m[u][v] = mod_w
for k in range(N):
for i in range(N):
if dist_f[i][k] == NEG_INF:
continue
for j in range(N):
if dist_f[k][j] == NEG_INF:
continue
cand = dist_f[i][k] + dist_f[k][j]
if cand > dist_f[i][j]:
dist_f[i][j] = cand
dist_m[i][j] = dist_m[i][k] * dist_m[k][j] % MOD
out = []
for _ in range(Q):
s, t = map(int, input().split())
s -= 1; t -= 1
if dist_f[s][t] == NEG_INF:
out.append(-1)
else:
out.append(dist_m[s][t])
print('\n'.join(map(str, out)))
solve()
Step-by-Step 解説
Step 1: 半環の定義
通常の加算・乗算の代わりに $(R, \max, \times)$ を使う「max-times 半環」(熱帯半環の変種)。パスの「コスト」として対数値を使い比較することで、積の最大化を和の最大化に変換できる。
Step 2: 対数変換によるフロート比較
辺の信頼度 $a/b$ の対数 $\log(a/b)$ を float で保持。比較はこの float 値で行い、実際の出力は mod 演算した値を別途管理する。
Step 3: Floyd-Warshall on 半環
通常の Floyd-Warshall の $+$(加算)を $\times$(乗算)に、$\min$(最小化)を $\max$(最大化)に置き換える。$O(N^3)$ で全点対最大積パスが求まる。
Step 4: modular 逆元による有理数処理
$a/b \bmod p = a \cdot b^{-1} \bmod p$($b^{p-2} \bmod p$ でフェルマーの小定理)。比較は float の対数で行い、mod 値は別管理。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 比較に mod 値を使う | mod は順序を保持しない | 比較は log float、出力は mod |
| 対角成分を 1 で初期化し忘れ | 自己ループの積は 1 | dist_m[i][i] = 1 必須 |
| NEG_INF の伝播チェック漏れ | 到達不可能なパスを処理してしまう | if dist_f[i][k] == NEG_INF: continue |
次のステップ
発展問題: min-plus 半環(通常の最短路)と max-times 半環を統合した「双基準最適パス」を求めよ。