公式
E - 観光ルートの平均スコア / Average Score of Tourist Routes 解説 by admin
gpt-5.3-codex概要
\(S\) から \(T\) へのすべての単純パスについて、各パス上の頂点重み(満足度)の合計を集計し、その平均を求める問題です。
\(N \le 18\) と小さいので、頂点集合をビットマスクで管理する部分集合 DP(bit DP)で全パスを数え上げます。
考察
まず「単純パスを全部列挙して平均を取る」ことをそのままやると、パス数はグラフによって指数的に増え、DFS 全列挙は間に合いません。
この制約で重要なのは \(N \le 18\) です。
頂点集合を \(2^N\) 通りのビットマスクで表せるので、
「どの頂点を使ったか」を状態に持つ DP が可能になります。
重要な設計
平均を求めたいので、本来ほしいのは
- パスの本数
- パススコア(各パスの頂点重み和)の総和
の 2 つです。
そこで状態ごとに次を持ちます:
dp_count[mask][v]:
「使った頂点集合がmask、終点がv」である \(S \to v\) の単純パス本数dp_sum[mask][v]:
上記パスたちのスコア総和
この 2 つを同時に遷移させると、最後に \(T\) で合流して平均を出せます。
遷移の意味
(mask, v) から未訪問隣接頂点 u に進むとき:
- 本数はそのまま加算:
dp_count[mask|{u}][u] += dp_count[mask][v] - スコア総和は、既存スコアに
uの重みを足す:
各パスで \(c_u\) 増えるので
dp_sum[mask|{u}][u] += dp_sum[mask][v] + dp_count[mask][v] * c[u]
この式がこの問題の核心です。
アルゴリズム
- 入力を受け取り、頂点を 0-index に直す。
- 隣接リストの代わりに、各頂点の隣接先をビット集合
adj[v]として持つ(高速に未訪問隣接を取り出すため)。 dp_count,dp_sumを2^N × Nで用意。- 初期状態:
start_mask = 1<<Sdp_count[start_mask][S] = 1dp_sum[start_mask][S] = c[S]
- 全
mask、全終点vについて遷移。
nxt = adj[v] & ~maskで未訪問隣接だけを抽出し、1bit ずつ取り出して更新。 Tを含むすべてのmaskについてtotal_count += dp_count[mask][T]total_sum += dp_sum[mask][T]
- 答えは平均
total_sum / total_count。
計算量
- 時間計算量: \(O(2^N \cdot N^2)\)(実装上は「各状態から隣接先へ」の合計)
- 空間計算量: \(O(2^N \cdot N)\)
※ \(N \le 18\) なので十分実行可能です。
実装のポイント
単純パス保証は
uを「未訪問(~mask)」に限定することで実現。平均を直接 DP せず、本数と総和を別々に管理するのが安全で実装しやすい。
Python では
intが多倍長なので、パス数・総和が大きくなってもオーバーフローしません。ビット走査(
lsb = x & -x)で未訪問隣接を高速列挙している点が効率的です。ソースコード
import sys
def main():
input = sys.stdin.readline
N, M, S, T = map(int, input().split())
S -= 1
T -= 1
c = [int(input()) for _ in range(N)]
adj = [0] * N
for _ in range(M):
u, v = map(int, input().split())
u -= 1
v -= 1
adj[u] |= 1 << v
adj[v] |= 1 << u
size = 1 << N
# dp_count[mask][v]: number of simple paths from S to v using exactly vertices in mask
# dp_sum[mask][v]: total score sum over those paths
dp_count = [[0] * N for _ in range(size)]
dp_sum = [[0] * N for _ in range(size)]
start_mask = 1 << S
dp_count[start_mask][S] = 1
dp_sum[start_mask][S] = c[S]
for mask in range(size):
if (mask & start_mask) == 0:
continue
for v in range(N):
cnt = dp_count[mask][v]
if cnt == 0:
continue
sm = dp_sum[mask][v]
nxt = adj[v] & (~mask)
while nxt:
lsb = nxt & -nxt
u = lsb.bit_length() - 1
nmask = mask | lsb
dp_count[nmask][u] += cnt
dp_sum[nmask][u] += sm + cnt * c[u]
nxt ^= lsb
total_count = 0
total_sum = 0
bit_t = 1 << T
for mask in range(size):
if (mask & bit_t) == 0:
continue
total_count += dp_count[mask][T]
total_sum += dp_sum[mask][T]
ans = total_sum / total_count
print("{:.10f}".format(ans))
if __name__ == "__main__":
main()
この解説は gpt-5.3-codex によって生成されました。
投稿日時:
最終更新: