Branched Evolution

Competitive Programming in Python

AOJ DPL_2_B 中国人郵便配達問題

重み付き無向グラフで、すべての辺を通る閉路のうち最短経路の距離を求める。

https://onlinejudge.u-aizu.ac.jp/problems/DPL_2_B

解説

グラフがオイラー閉路をもつ場合、すべての辺の重みの和が答えになる。そうでない場合、次数が奇数の頂点(奇点)が偶数個存在する。奇点同士でペアを作り、間に辺を追加することでオイラー閉路が作れる。実際には辺を追加するわけではなく最短経路をもう一度辿ることになる。奇点同士のマッチングを全探索し、最短経路の和の最小値を求め、すべての辺の重みの和を足したものが答えになる。

実装

長さが偶数のリストからマッチングを列挙するジェネレーターを使う。

def generate_matching(lst):
    n = len(lst)
    if n == 0:
        yield []
        return
    x = lst[0]
    for i in range(1, n):
        y = lst[i]
        res = [z for z in lst if z != x and z != y]
        for matching in generate_matching(res):
            yield [(x, y)] + matching

例えば、$[0,1,2,3]$の場合は以下のように列挙できる。

for matching in generate_matching([0, 1, 2, 3]):
    print(matching)
# [(0, 1), (2, 3)]
# [(0, 2), (1, 3)]
# [(0, 3), (1, 2)]

全点間の距離を求めるにはワーシャル・フロイド法を使う。

from collections import defaultdict

INF = 1 << 30
n, m = map(int, input().split())
edge = [tuple(map(int, input().split())) for _ in range(m)]
# 隣接リスト構築
graph = [defaultdict(lambda: INF) for _ in range(n)]
for x, y, d in edge:
    graph[x][y] = min(graph[x][y], d)
    graph[y][x] = min(graph[y][x], d)
# ワーシャルフロイド
dist = [[INF] * n for _ in range(n)]
for x in range(n):
    dist[x][x] = 0
    for y in graph[x]:
        dist[x][y] = graph[x][y]
for z in range(n):
    for x in range(n):
        for y in range(n):
            dist[x][y] = min(dist[x][y], dist[x][z] + dist[z][y])
# 次数が奇数の点を列挙
deg = [0] * n
for x, y, d in edge:
    deg[x] += 1
    deg[y] += 1
odd_lst = [x for x in range(n) if deg[x] % 2]
# 最小マッチング
min_cost = INF
for matching in generate_matching(odd_lst):
    min_cost = min(min_cost, sum(dist[x][y] for x, y in matching))
print(min_cost + sum(d for x, y, d in edge))