Day 164-Q4 — 2026-10-02 | 橋をなくすために足す辺の最小数(橋木の葉の数 L に対し ⌈L/2⌉。再帰なしの lowlink と、多重辺でも壊れない親辺の判定)

2026-10-02 赤色 Master (Phase 8+) ★★★★★★★★★ グラフ(焦点帯外 M2)。連結グラフを 2 辺連結にする最小の辺の追加数は、2 辺連結成分を縮約した木(橋木)の葉の数の半分(切り上げ)。橋の本数でも、元のグラフの次数 1 の頂点数でもない

問題

$N$ 頂点 $M$ 辺の連結な単純無向グラフが与えられる。辺をいくつか追加して、どの 1 辺を取り除いても連結のまま(2 辺連結、橋が無い)にしたい。追加する辺の最小の本数を求めよ。追加する辺は、すでにある辺と同じ頂点対を結ぶもの(多重辺)でもよいが、自己ループは不可。

入力形式

N M
u_1 v_1
...
u_M v_M

制約

#条件
1$1\le N\le2\times10^5$
2$N-1\le M\le3\times10^5$
3$1\le u_i<v_i\le N$、同じ辺は 2 度現れない、グラフは連結
4入力はすべて整数

入出力例

入力例1

5 4
1 2
2 3
3 4
4 5

出力例1

1

(パスの両端 1 と 5 を結べば全体が 1 つの閉路になる)

入力例2

7 6
1 2
1 3
1 4
1 5
1 6
1 7

出力例2

3

(星形。葉が 6 つなので 3 本。葉を 2 つずつ組にして結ぶ)

入力例3

4 4
1 2
2 3
1 3
3 4

出力例3

1

(三角形 1-2-3 に頂点 4 がぶら下がっている。橋は辺 3-4 の 1 本。4 と、三角形の中の頂点(1 でも 2 でも 3 でもよい)を結べばよいので 1 本)

背景と出典

橋を除いて残る連結成分(2 辺連結成分)を 1 点に縮約すると木ができる(橋木、bridge tree)。この木の葉の数を $L$ とすると、2 辺連結にするのに必要な追加辺の最小数は $\lceil L/2\rceil$ になる(木が 1 点なら 0)。この結果は私の記憶では K. P. Eswaran と R. E. Tarjan の 1976 年の論文(Augmentation problems, SIAM J. Computing)に遡る。本セッションでは論文を開いておらず、帰属は未確認。正しさの根拠は、辺の追加の総当たり(下の検証ログ)。

概念図

元のグラフ(入力例 3 を拡張)と、2 辺連結成分を縮約した橋木 1 2 3 4 5 6 7 赤い辺 = 橋(4 本)。三角形 {1,2,3} は 1 つの 2 辺連結成分 橋木 123 4 5 6 7 葉 L = 3(緑)→ 答え ⌈3/2⌉ = 2 葉を 2 つずつ組にして結ぶと、パス上の橋がすべて閉路に入る 橋の本数(4)や元の次数 1 の頂点数(2)では答えが合わない

ヒント(クリックで段階的に開示)

ヒント1(方向性)

橋でない辺だけでつながる部分は、これ以上いじる必要がない。そこを 1 点にまとめた木を考える。その木の「葉」にあたる成分は、今は 1 本の橋でしか外とつながっていない。

ヒント2(アプローチ)

葉の成分が 1 つ残る限り、その成分の橋は消えない。1 本の追加辺は、木の上の 2 点を結ぶので、そのパス上の橋をすべて消せる。端点に葉を選べば、一度に 2 つの葉を片づけられる。だから必要な本数は $\lceil L/2\rceil$。

ヒント3(誘導)

橋の見つけ方は lowlink(DFS の訪問順 order と、そこから辿れる最小の訪問順 low)。子 $u$ から親 $p$ への辺について low[u] > order[p] なら橋。$N=2\times10^5$ なのでパス状のグラフで再帰が深くなる。再帰を使わず、スタックに「次に見る隣接の位置」を持たせる。

模範解答 (Python)

模範解答を表示する(まず自力で解いてから)
import sys


def main():
    data = sys.stdin.buffer.read().split()
    n, m = int(data[0]), int(data[1])
    g = [[] for _ in range(n)]          # (隣接頂点, 辺番号)
    for i in range(m):
        u, v = int(data[2 + 2 * i]) - 1, int(data[3 + 2 * i]) - 1
        g[u].append((v, i))
        g[v].append((u, i))
    # 橋を探す(再帰なしの lowlink)
    order = [-1] * n
    low = [0] * n
    is_bridge = [False] * m
    t = 0
    stack = [(0, -1, 0)]                # (頂点, 親へ来た辺番号, 次に見る隣接の位置)
    order[0] = low[0] = 0
    t = 1
    while stack:
        u, pe, idx = stack.pop()
        if idx < len(g[u]):
            stack.append((u, pe, idx + 1))
            v, e = g[u][idx]
            if e == pe:                 # 来た辺そのものは戻らない(辺番号で判定。多重辺があっても正しい)
                continue
            if order[v] == -1:
                order[v] = low[v] = t
                t += 1
                stack.append((v, e, 0))
            elif order[v] < low[u]:
                low[u] = order[v]
        elif pe != -1:
            # u の探索が終わった。親 p へ low を伝え、橋かどうか決める
            p = None
            for v, e in g[u]:
                if e == pe:
                    p = v
                    break
            if low[u] < low[p]:
                low[p] = low[u]
            if low[u] > order[p]:
                is_bridge[pe] = True
    # 橋を除いた連結成分(2 辺連結成分)に番号を振る
    comp = [-1] * n
    c = 0
    for s in range(n):
        if comp[s] != -1:
            continue
        comp[s] = c
        st = [s]
        while st:
            u = st.pop()
            for v, e in g[u]:
                if not is_bridge[e] and comp[v] == -1:
                    comp[v] = c
                    st.append(v)
        c += 1
    # 橋木での次数。葉(次数 1)の数 L に対して答えは ceil(L/2)
    deg = [0] * c
    for u in range(n):
        for v, e in g[u]:
            if is_bridge[e]:
                deg[comp[u]] += 1
    leaves = sum(1 for d in deg if d == 1)
    print((leaves + 1) // 2)


main()

計算量: $O(N+M)$。実測(Python 3.14.7、M1 Mac)で $N=2\times10^5$ のパス 0.29 s、ランダムな木 0.52 s、星形 0.29 s、ランダムな木に辺を 10 万本足した($M\approx3\times10^5$)グラフ 0.89 s。

Step-by-Step 解説

1橋を lowlink で見つける

DFS で各頂点の訪問順 order と、「子孫の後退辺で到達できる最小の訪問順」low を求める。親 $p$ から子 $u$ への辺は、low[u] > order[p] のとき橋($u$ の部分木から $p$ より上へ戻る道が無い)。

2「親へ戻らない」は辺番号で判定する

無向グラフの DFS では、来た辺を逆に辿って親へ戻る動きを後退辺と数えてはいけない。頂点番号で「親には戻らない」と判定すると、同じ頂点対を結ぶ多重辺があるとき、2 本目の辺を見落として橋と誤判定する。本問の入力は単純グラフだが、辺番号で判定する書き方は多重辺でも正しく、橋木を作る一般の場面でそのまま使える。

3橋木を作り、葉を数える

橋を除いて連結成分に番号を振り、各成分が接する橋の本数(橋木での次数)を数える。次数 1 の成分が葉。連結グラフなので橋木も連結な木。橋が無ければ成分が 1 個で、次数 0 の頂点が 1 個だけ。葉は 0 個になり、答えは 0。

4なぜ $\lceil L/2\rceil$ か

下界: 葉の成分ごとに、その成分に接する橋は 1 本。この橋を消すには、追加辺の少なくとも 1 本の端点がその葉の成分の中になければならない。1 本の追加辺は端点が 2 つなので、葉を $L$ 個片づけるには $\lceil L/2\rceil$ 本以上要る。上界(その本数で足りること)の証明は、木の葉を「うまく組にして」結ぶ構成が要る。本ファイルでは証明せず、総当たりで確かめた(下の検証ログ)。

5橋の本数や次数 1 の頂点数では駄目

答えは橋の数に比例しない(パスは橋が $N-1$ 本でも答えは 1)。「元のグラフの次数 1 の頂点数」で数えると、葉の成分が閉路や三角形のときに外れる(入力例 3 は次数 1 の頂点が頂点 4 だけで $\lceil1/2\rceil=1$ とたまたま合うが、三角形の側にも 1 つ葉があるので $L=2$)。

迷った他案と採らなかった理由

案計算量採否理由
辺の追加を総当たりして橋が無くなるか確かめる指数検算用のみ$N\le7$ なら $k\le3$ 本まで全探索できる。検算に使った
橋の本数を答える$O(N+M)$不採用パスの橋は $N-1$ 本だが答えは 1。400 件中 238 件誤答
葉の数 $L$ から $L-1$$O(N+M)$不採用$L=2,3$ では合うが $L\ge4$ で外れる。400 件中 23 件誤答
$\lfloor L/2\rfloor$$O(N+M)$不採用$L$ が奇数のとき 1 本足りない。400 件中 72 件誤答
元のグラフの次数 1 の頂点数を使う$O(N+M)$不採用葉の成分が閉路を含むと外れる。400 件中 5 件誤答(小さい入力では当たりやすく危険)
再帰の DFS で lowlink$O(N+M)$不採用パス状の入力で再帰の深さが $N$。Python では再帰上限の引き上げか別実装が要る
再帰なしの lowlink+橋木の葉(本解)$O(N+M)$採用総当たりと 400 件全件一致

前提が変わったら:

  • 頂点連結度(関節点)で 2 連結にしたいなら、橋木ではなくブロック・カット木を作り、葉ブロックを数える。式は同じ形($\lceil L/2\rceil$)だが、葉ブロックが 2 点以上のときに端点の選び方に注意が要る
  • 辺に追加の費用や重みがある(どの頂点対を結べるかに制限がある)と、最小本数の式は使えず NP 困難になる問題群に入る(既知と記憶するが、本セッションで確認していない)
  • 非連結なら、先に連結にする辺が要る。孤立点や木の本数まで含めた式が必要で、本問の式は使えない

よくあるミス

ミス原因正しい書き方
橋の本数を答える「橋を 1 本ずつ消す」と思う橋木の葉の数 $L$ を数え $\lceil L/2\rceil$(238/400 誤答)
$\lfloor L/2\rfloor$ にする切り上げを忘れる(L + 1) // 2(72/400 誤答)
頂点番号で「親に戻らない」を判定する多重辺があると、親への 2 本目の辺を無視する辺番号 e == pe で判定する
再帰で lowlink を書く木・パスで深さ $N$明示的なスタックに「次に見る位置」を持たせる
$N=1$ で落ちる橋木が 1 点で、次数 0 の頂点しかない葉は次数 1 の成分だけを数える(葉 0 個 → 答え 0)

次のステップ

  • 発展1: 追加する辺の具体的な端点の組を出力する(橋木の葉を DFS 順に並べ、$i$ 番目と $i+\lceil L/2\rceil$ 番目を結ぶ、という構成を調べる)
  • 発展2: 頂点連結度版(2 頂点連結、ブロック・カット木)で同じ問題を解く
  • 次回予告: 焦点帯 M3(数論)

検証ログ(本セッションで実行した数値)

  • 総当たりとの一致(実測): $N\le7$ のランダムな連結グラフ(ランダムな木に辺を 0〜3 本足したもの)400 件で、辺を $k=0,1,2,\dots$ 本(多重辺可)追加して橋が無くなる最小の $k$ を全探索し、プログラム全体の出力と全件一致。答えの分布は 0: 54、1: 254、2: 89、3: 3。**答えが 3 の件は 3 件しかなく、$L\ge5$ の大きい場合の裏付けは薄い**
  • 誤った方法(実測): 橋の本数 238/400、$L-1$ 23/400、$\lfloor L/2\rfloor$ 72/400、元の次数 1 の頂点数から $\lceil\cdot/2\rceil$ 5/400 で誤答
  • 入力例(実測): 例 1 は 1、例 2 は 3、例 3 は 1。$N=2$ のパス 1 本は 1(多重辺が必要)、$N=1$ は 0
  • 実行時間(実測): $N=2\times10^5$ のパス 0.29 s、ランダムな木 0.52 s、星形 0.29 s、$M=3\times10^5$ 規模 0.89 s
  • 扱っていない範囲: 上界($\lceil L/2\rceil$ 本で必ず足りること)の証明、$L\ge5$ の総当たり、追加辺の復元、Eswaran–Tarjan の原典確認

自己評価

自分の回答

気づき・メモ