公式

G - Colorful Christmas Tree 解説 by toam


RGB をそれぞれ 色 \(1,2,3\) として扱います.頂点集合を二部グラフの彩色になるように \(2\) つの頂点集合に分け,一方を左側頂点,他方を右側頂点と呼ぶことにします.

頂点 \(v\) の色が \(k\) のときに行われる,頂点 \(v\) に隣接する辺削除回数はあらかじめ決まっています.その回数を \(A_{v, k}\) とします.

すべての辺を削除することができる操作列が存在するような必要条件を考えるうえで,次のようなグラフの最大流問題に言い換えます.

  • 頂点は,\((v, k)\ (1\leq v\leq N, 1\leq k\leq 3)\) および \(\mathrm{source}, \mathrm{sink}\)\(3N+2\) 個である.
  • 各左側頂点 \(u\) について,頂点 \(\mathrm{source}\) から 頂点 \((u, k)\ (1\leq k\leq 3)\) に容量 \(A_{u, k}\) の辺を追加する.
  • 各右側頂点 \(v\) について,頂点 \((v, k)\ (1\leq k\leq 3)\) から頂点 \(\mathrm{sink}\) に容量 \(A_{v, k}\) の辺を追加する.
  • 左側頂点 \(u\) と右側頂点 \(v\) を結ぶ木の辺が存在したとき,頂点 \((u, k_1)\) から頂点 \((v, k_2)\ (1\leq k_1\leq 3, 1\leq k_2\leq 3, k_1\neq k_2)\) に容量 \(1\) の辺を追加する.

操作列が存在するならば,このグラフの最大流の流量は \(N-1\) です.(各操作における辺およびそのときの両端の色が,\(\mathrm{source}\) から \(\mathrm{sink}\) への \(1\) つの水流に対応します.また,明らかに最大流の流量の上界は \(N-1\) です.)

逆にこのとき操作列は存在します.次が言えればよいです.

  • 最大流において,頂点 \((u, k_1)\) から頂点 \((v, k_2)\)\(u\) は左側頂点,\(v\) は右側頂点,\((1\leq k_1\leq 3, 1\leq k_2\leq 3, k_1\neq k_2)\) )へ水が流れているとき,辺 \((u, v)\) は頂点 \(u\) が色 \(k_1\),頂点 \(v\) が色 \(k_2\) のときにその辺の削除操作を行うと決め打つことにする.これらをすべて満たすような操作列が存在する.

\(N\) についての帰納法により,決め打った操作のうちいずれかが初手の操作として可能であることを言えればよく,背理法で証明できます.

証明
最大流で求めた,辺 \((u,v)\) を削除するときの頂点 \(u,v\) の色をそれぞれ \(a(u,v),a(v,u)\) とおくことにする.

適当な頂点を根とする.根から最も遠い頂点 \(v\) とその親 \(p\) について注目する.\(v\) は次数 \(1\) であるから \(a(v,p)=c_v\) であり,また背理法の仮定より \(a(p,v)\neq c_p\) である.このことは \(p\)\(v\) 以外の子にも言える.\(p\) と繋がる辺のうち少なくともどれか 1 つは色が \(c_p\) の状態で処理する必要があるため,\(p\) の親を \(q\) としたとき,\(a(p,q)=c_p, a(q,p)\neq c_q\) が言える.この議論を繰り返すと,根以外の頂点 \(u\) に対して,その親を \(p(u)\) とすれば \(a(u,p(u))=c_u,a(p(u),u)\neq c_{p(u)}\) が成り立つが,これは根に対して矛盾.

実装について,現在どの辺が削除可能なのかを \(O(N)\) かけて調べる, というのを愚直に \(N-1\) 回繰り返しても計算量は \(O(N^2)\) となり間に合います.フローの計算量も \(O(N^2)\) です.

from atcoder.maxflow import MFGraph


def solve():
    n = int(input())
    c = [0 if i == "R" else 1 if i == "G" else 2 for i in input()]
    edge_id = {}
    G = [[] for i in range(n)]
    for i in range(n - 1):
        a, b = map(lambda x: int(x) - 1, input().split())
        edge_id[(a, b)] = edge_id[(b, a)] = i
        G[a].append(b)
        G[b].append(a)

    bipartite = [-1] * n
    bipartite[0] = 0
    mf = MFGraph(3 * n + 2)
    S = 3 * n
    T = S + 1

    stc = [0]
    while stc:
        v = stc.pop()
        cnt = [0] * 3
        tmp = c[v]
        for i in range(len(G[v])):
            cnt[tmp] += 1
            tmp = (tmp + 1) % 3

        for i in range(3):
            if bipartite[v] == 0:
                mf.add_edge(S, 3 * v + i, cnt[i])
            else:
                mf.add_edge(3 * v + i, T, cnt[i])

        if bipartite[v] == 0:
            for u in G[v]:
                for i in range(3):
                    for j in range(3):
                        if i != j:
                            mf.add_edge(3 * v + i, 3 * u + j, 1)

        for u in G[v]:
            if bipartite[u] == -1:
                bipartite[u] = bipartite[v] ^ 1
                stc.append(u)

    f = mf.flow(S, T)
    if f != n - 1:
        print("No")
        return

    op = []
    for e in mf.edges():
        if e.flow == 1 and 0 <= e.src < 3 * n and 0 <= e.dst < 3 * n:
            u, x = e.src // 3, e.src % 3
            v, y = e.dst // 3, e.dst % 3
            op.append((u, x, v, y))

    ans = []
    while op:
        for u, x, v, y in op:
            if c[u] == x and c[v] == y:
                op.remove((u, x, v, y))
                ans.append(edge_id[(u, v)])
                c[u] = (c[u] + 1) % 3
                c[v] = (c[v] + 1) % 3
                break

    print("Yes")
    print(*[i + 1 for i in ans])


for _ in range(int(input())):
    solve()

投稿日時:
最終更新: