問題
$N$ 頂点 $M$ 辺の無向グラフで各辺が黒 $(c=0)$ か白 $(c=1)$ に着色されている。黒辺 $\le B$ 本・白辺 $\le W$ 本でスパニングツリーを構成できるか判定し、可能なら黒辺最小の本数と白辺本数を出力せよ。
制約
$2 \le N \le 2 \times 10^5$
$N-1 \le M \le 3 \times 10^5$
$0 \le B, W \le N-1$
時間制限: 2sec / メモリ: 256MB
入出力例
入力例 1
4 5 2 3
1 2 0
2 3 0
3 4 1
1 4 1
2 4 0
出力例 1
Yes 2 1
概念図: 実現可能な黒辺数の区間
ヒント(段階的開示)
ヒント1: 方向性
スパニングツリーで使う黒辺数 $b$ は連続した整数区間 $[b_{\min}, b_{\max}]$ をとる(Matroid Exchange Lemma)。制約 $b \le B$ かつ $b \ge N-1-W$ との交点を求めるだけでよい。
ヒント2: アプローチ
- 黒辺優先 Kruskal で $b_{\max}$(黒辺を最大化)
- 白辺優先 Kruskal で $b_{\min}$(黒辺を最小化)
- 制約との交点: $lo = \max(b_{\min}, N-1-W)$, $hi = \min(b_{\max}, B)$
- $lo \le hi$ なら Yes、黒辺 $lo$ 本で出力
ヒント3: 実装骨格
def kruskal(prefer_black):
# prefer_black=True: 黒辺(c=0)を優先してソート
uf = UF(N)
bc = wc = 0
key = lambda e: e[2] if prefer_black else -e[2]
for u, v, c in sorted(edges, key=key):
if uf.union(u, v):
if c == 0: bc += 1
else: wc += 1
total = bc + wc
return bc, total # 使用黒辺数, 総辺数
max_b, t1 = kruskal(True)
min_b, t2 = kruskal(False)
if t1 < N-1: # スパニングツリー不成立
print("No"); return
lo = max(min_b, N-1-W)
hi = min(max_b, B)
if lo > hi: print("No")
else: print(f"Yes {lo} {N-1-lo}")
模範解答 (Python)
import sys
def solve():
data = sys.stdin.read().split()
idx = 0
N, M, B, W = int(data[idx]), int(data[idx+1]), int(data[idx+2]), int(data[idx+3])
idx += 4
edges = []
for _ in range(M):
u, v, c = int(data[idx])-1, int(data[idx+1])-1, int(data[idx+2])
edges.append((u, v, c))
idx += 3
class UF:
def __init__(self, n):
self.p = list(range(n))
self.r = [0] * n
def find(self, x):
while self.p[x] != x:
self.p[x] = self.p[self.p[x]]
x = self.p[x]
return x
def union(self, x, y):
x, y = self.find(x), self.find(y)
if x == y: return False
if self.r[x] < self.r[y]: x, y = y, x
self.p[y] = x
if self.r[x] == self.r[y]: self.r[x] += 1
return True
def kruskal_count(prefer_black):
uf = UF(N)
bc = wc = 0
for u, v, c in sorted(edges, key=lambda e: e[2] if prefer_black else -e[2]):
if uf.union(u, v):
if c == 0: bc += 1
else: wc += 1
return bc, bc + wc
max_b, total = kruskal_count(True)
if total < N - 1:
print("No")
return
min_b, _ = kruskal_count(False)
lo = max(min_b, N - 1 - W)
hi = min(max_b, B)
if lo > hi:
print("No")
else:
print(f"Yes {lo} {N-1-lo}")
solve()
Step-by-Step 解説
1Matroid Exchange Lemma
グラフマトロイドでは、2つの基(スパニングツリー)$T_1, T_2$ が存在すれば、$T_1$ から辺を1本除き $T_2$ から1本追加して新しい基を作れる。この交換の繰り返しで黒辺数を連続的に変化させられる → 実現可能な黒辺数は連続区間。
グラフマトロイドでは、2つの基(スパニングツリー)$T_1, T_2$ が存在すれば、$T_1$ から辺を1本除き $T_2$ から1本追加して新しい基を作れる。この交換の繰り返しで黒辺数を連続的に変化させられる → 実現可能な黒辺数は連続区間。
2黒辺優先 Kruskal
辺を $c=0$(黒)優先でソート(黒辺は白辺より先に選ばれる)して Kruskal を実行。得られた木は黒辺数 $b_{\max}$ の木。
辺を $c=0$(黒)優先でソート(黒辺は白辺より先に選ばれる)して Kruskal を実行。得られた木は黒辺数 $b_{\max}$ の木。
3白辺優先 Kruskal
同様に $c=1$ 優先でソートすると黒辺数 $b_{\min}$ の木が得られる。
同様に $c=1$ 優先でソートすると黒辺数 $b_{\min}$ の木が得られる。
4制約との交点
制約 $b \le B$ と白辺数 $N-1-b \le W$(→ $b \ge N-1-W$)を合わせると有効範囲 $[lo, hi]$。空集合なら No。
制約 $b \le B$ と白辺数 $N-1-b \le W$(→ $b \ge N-1-W$)を合わせると有効範囲 $[lo, hi]$。空集合なら No。
計算量
Kruskal ソート: $O(M \log M)$
Union-Find: $O(M \cdot \alpha(N)) \approx O(M)$
合計: $O(M \log M)$(Kruskal 2回)
Union-Find: $O(M \cdot \alpha(N)) \approx O(M)$
合計: $O(M \log M)$(Kruskal 2回)
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 連続性の仮定漏れ | Exchange Lemma を知らない | 区間 [lo, hi] で判定する |
| グラフ非連結のケース | total < N-1 の確認忘れ | Kruskal の総辺数をチェック |
| 白辺の上限 W の変換ミス | b + (N-1-b) = N-1 の関係 | b >= N-1-W として下限に変換 |
次のステップ
- 発展問題: 3色辺で各色 $\le k$ 本のスパニングツリー(Matroid Intersection 一般化)
- 類題: AtCoder ARC 076 D "Built?"
- 応用: 重み付き Matroid Intersection(最小重みの色制約スパニングツリー)