AtCoder上にある問題のうち、AtCoder Problemsでdiff 800以上と判定されているものを順番に解いていく企画。
基本的な考え方は全てコード中のコメントに入れてあるので、参照のこと。
出典:
AtCoder Beginner Contest 218 E – Destruction
前から考えるのが難しければ、後ろから考えよう。
ちなみにこの問題、コンテスト中に2400人近くが解いているのがやや驚き。個人的には、難易度はそれなりにありそうに思うんだけど……。
# AtCoder Beginner Contest 218 E - Destruction
# https://atcoder.jp/contests/abc218/tasks/abc218_e
# tag: グラフ 木 最小全域木 クラスカル法 Union_Find
# グラフ上から辺を取り除きつつ、その都度連結判定を行うのは難しい。
# そこで、逆に点数が少ない辺を用いて全ての頂点を繋げることを考える。
# つまり、最初は頂点のみがある状態からスタートし、点数が
# 低い辺を順番に用いて徐々にグラフを連結していく。
# 全ての頂点が連結された時、使用しなかった辺を報酬に
# 変換できる……といった感じになる。
# この時、注意事項が 2つある。
# 一つは、点数が負の辺は必ず用いることにするということ。
# もう一つは、点数が正で、かつ用いる必要がない辺、
# つまり既に連結済みの頂点同士を結ぶ辺は使用しない、とすること。
# 頂点同士が連結かどうかに関しては、Union_Findを用いて
# 管理を行うと実装しやすい。
# ちなみに、この条件から点数が負の辺を用いる~という条件を
# なくせば、クラスカル法と呼ばれる最小全域木の構成方法となる。
# Union_Find クラス
class Union_Find:
# 親管理リストと高さ管理リストを初期化し、
# 要素N個のUnion-Find森を作成する。
# 親管理リストは、基本的には自分のひとつ上の親を表すが、
# 値が負の場合には、自身が最上位の親(リーダー)であることを表し、
# 自分を含めたグループの人数を管理することとする
def __init__(self, N):
self.parent = [-1] * N
self.rank = [0] * N
self.group_count = N
self.N = N
# xの所属するグループのリーダーを返す
def find(self, x):
# 自分自身がリーダーなら、自分を返す
if self.parent[x] < 0:
return x
# 再帰的に捜索し、見つかれば繋ぎ変えておく
# (計算量が増える=面倒くさいので)高さ管理は行わない
par = self.find(self.parent[x])
self.parent[x] = par
return par
# xとyのグループを統合する
def unite(self, x, y):
# それぞれのリーダーに対する操作を行うことになる
x = self.find(x)
y = self.find(y)
# リーダーが同じなら何もする必要がない
if x == y:
return (-1, -1)
# 木の高さが同じ場合:
# グループの人数を合計しつつ適当に繋ぎ、繋げられた方の高さを1増やす
if self.rank[x] == self.rank[y]:
self.parent[x] += self.parent[y]
self.parent[y] = x
self.rank[x] += 1
self.group_count -= 1
return (x, y)
# 木の高さが違うなら、低い方を高い方につなぐ
if self.rank[x] < self.rank[y]:
x, y = y, x
self.parent[x] += self.parent[y]
self.parent[y] = x
self.group_count -= 1
return (x, y)
# xとyが同じグループかどうかを調べる
def samep(self, x, y):
return self.find(x) == self.find(y)
# ここからメイン。
def main():
N, M = map(int, input().split())
path_dat = [list(map(int, input().split())) for _ in range(M)]
# 点数をもとに辺をソートしておく。
path_dat.sort(key=lambda x: x[2])
uft = Union_Find(N)
result = 0
for a, b, c in path_dat:
a -= 1
b -= 1
# まだ連結でない頂点を結ぶ or 点数が負なら、
# 辺を使用する(=残す)。
if uft.find(a) != uft.find(b) or c < 0:
uft.unite(a, b)
# でなければ、辺を使用しない(=取り除く)
else:
result += c
print(result)
main()