Official
C - チームの旗の色 / Team Flag Colors Editorial by admin
gpt-5.3-codex概要
「チームの合併」と「合併後チームの色の上書き」を大量に処理したあと、最終的に存在する各チームの旗色の種類数を数える問題です。
チーム管理には Union-Find(DSU)を使うのが最適です。
考察
この問題の本質は、選手ごとの所属ではなく「どの連結成分(チーム)に属しているか」を高速に管理することです。
操作ごとに
- \(U_i\) と \(V_i\) の属するチームを統合(同じならそのまま)
- そのチームの色を \(C_i\) に上書き
を行います。
重要な気づき
- 各操作は「2頂点を結ぶ」ので、チームは連結成分として扱える。
- 連結成分の管理は Union-Find が定番。
- 色は「チーム単位」で1つだけ持てばよい(メンバーごとに持つ必要はない)。
素朴解が厳しい理由
例えば毎回、 - 全選手を走査してチームを再構築する - 2チームのメンバー一覧をマージする
のような実装をすると、最悪で \(O(NM)\) に近くなり、\(N=2\times10^5,\ M=1.5\times10^5\) では間に合いません。
どう解決するか
Union-Find で以下を管理します。
parent[x]: 親size[x]: 根がxの集合サイズ(union by size 用)color[x]: 根xが表すチームの現在色(未設定は 0)
操作 \((u,v,c)\) ごとに根 ru, rv を求め、
ru != rvなら小さい集合を大きい集合へ併合し、新しい根の色をcにするru == rvならその根の色をcで上書き
最後に「根であり、色が未設定でないもの」だけを見て色を set に入れれば、異なる色数が求まります。
アルゴリズム
- 初期化
- 各選手は自分自身を根とする集合
colorはすべて0(未設定)
- 各選手は自分自身を根とする集合
- 各操作 \((u,v,c)\) を処理
ru = find(u),rv = find(v)ru != rvの場合
sizeが大きい方を新しい根にする(union by size)- 親をつなぐ
- サイズ更新
- 新しい根の
colorをcにする
ru == rvの場合
- その根の
colorをcにする
- その根の
- 全要素
i=1..Nについて
parent[i] == i(根)かつcolor[i] != 0のときseenに追加
len(seen)を出力
計算量
- 時間計算量: \(O((N+M)\alpha(N))\)(ほぼ線形)
- 空間計算量: \(O(N)\)
実装のポイント
findは経路圧縮つきで実装し、高速化する。併合時は
sizeによるマージで木の高さを抑える。色は「根にのみ意味がある」ので、最終集計でも根だけ見る。
色の未設定判定に
0を使うため、入力色C_iが正整数である制約と矛盾しない。ソースコード
import sys
def main():
input = sys.stdin.readline
N, M = map(int, input().split())
parent = list(range(N + 1))
size = [1] * (N + 1)
color = [0] * (N + 1) # 0 means unset
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
for _ in range(M):
u, v, c = map(int, input().split())
ru = find(u)
rv = find(v)
if ru != rv:
if size[ru] < size[rv]:
ru, rv = rv, ru
parent[rv] = ru
size[ru] += size[rv]
color[ru] = c
else:
color[ru] = c
seen = set()
for i in range(1, N + 1):
if parent[i] == i and color[i] != 0:
seen.add(color[i])
print(len(seen))
if __name__ == "__main__":
main()
この解説は gpt-5.3-codex によって生成されました。
posted:
last update: