G - Not Only Tree Game Editorial by MMNMM

理詰め方針の概略

公式解説では、はじめに勝敗の判定方法を与え、それを示す形で解説が行われています。 ゲーム系の問題では、小さなケースで実験を行ったのちにその結果の正当性を証明する方針がよく取られますが、そのように天下りで結果を与えずとも解くことができることがあります。

この解説では、本問題の解法を(なるべく)天下り的に事実を与えることなしに解説することを目指します。


まず、次の補題を示します。

補題

ゲームの状態についてのある条件 \(P\) が存在して、ゲームが条件 \(P\) を満たす \(\Rightarrow\) その盤面は先手必勝である(その盤面で回すと負ける)が成り立っているとき、ゲームが条件 \(P\) を満たすようにする手を合法手から除外しても勝敗は変わらない。

証明

もとのゲームについての帰納法で示します。

もとのゲームの任意の合法手に対して、それぞれの合法手に対してそれを打ったあとの盤面が先手必勝か後手必勝かが定まっています。

それらのうちに後手必勝のものがあるならその盤面は条件 \(P\) を満たさず、それに遷移する手は変わらず合法手です。 帰納法の仮定より、打ったあとの盤面は後手必勝の盤面になります。 よって、このときは先手必勝となり、勝敗は変わりません。

それらのうちに先手必勝のものしかないなら、もとのゲームの合法手のうち一部(もしくは全部)が制限されても(帰納法の仮定より)遷移した結果の盤面はすべて先手必勝の盤面です。 よってこのときは後手必勝となり、勝敗は変わりません。

以上より、示されました。\(\square\)

天下り式に事実を与えることはないと言いながら補題を導入していますが、「負け確定の手は打てないことにしても変わらない」という事実を厳密に言っているものです。

この補題は証明の後半で使います。


このゲームでは、手を打つごとに \(1\) 本ずつ辺が増えていきます。 つまり、青木くんが手を打ったあとの辺数の偶奇は必ず \(M\) と逆になり、高橋くんが手を打ったあとの辺数の偶奇は必ず \(M\) と同じになります。

このことから、勝敗が確定したとき(合法手がなくなったとき)の辺の本数の偶奇がわかれば、どちらが勝つかもわかるといえます。

いま、合法手がなくなったときにグラフは完全二部グラフになっています。 このグラフに含まれる辺の本数は、完全二部グラフの極大な \(2\) つの独立集合 \(U,V\) の大きさ \(|U|,|V|\) について \(|U||V|\) 本です。

特に、\(|U|,|V|\) の少なくとも一方が偶数のときこれは偶数であり、いずれも奇数のときこれは奇数になります。

\(N\) が奇数のとき必ず \(|U|,|V|\) の偶奇は異なるため、次のことがわかります。

勝敗判定基準 1

\(N\) が奇数のとき、終局時に辺は偶数本ある。 つまり、

  • \(M\) が偶数なら、高橋くんが勝つ。
  • \(M\) が奇数なら、青木くんが勝つ。

\(N\) が偶数のとき、\(|U|,|V|\) はいずれも奇数であるか、いずれも偶数であるかのどちらか一方です。 最終的な辺の数について考えることで、次のことがわかります。

勝敗判定基準 2

\(N\) が偶数のとき、終局時のグラフの \(2\) つの極大な独立集合 \(U,V\) について、

  • \(M\) の偶奇と \(|U|\) の偶奇が等しいなら、高橋くんが勝つ。
  • \(M\) の偶奇と \(|U|\) の偶奇が異なるなら、青木くんが勝つ。

(ここやこれ以降のステートメントで登場する \(M\) は、グラフに対して操作を行ったあとの辺の本数ではなく、ゲーム開始時の辺の本数のことです。)

以下、頂点数が奇数の連結成分を奇成分、頂点数が偶数の連結成分を偶成分と呼ぶことにします。

連結成分がすべて偶成分のとき、どのように連結しても最終的な \(|U|\) の偶奇は変わりません。 よって、以下のことがわかります。

勝敗判定基準 3

連結成分がすべて偶成分のとき、適当にすべてを連結にしたのち \(|U|\) の偶奇を求めることで、勝敗判定基準 \(2\) によって勝敗を判定することができる。

より直接的には、以下のことが言える。 連結成分のうち、極大な独立集合の大きさが奇数であるようなものを \(O\) 個としたとき、

  • \(M\) の偶奇と \(O\) の偶奇が等しいなら、高橋くんが勝つ。
  • \(M\) の偶奇と \(O\) の偶奇が異なるなら、青木くんが勝つ。

また、奇成分が \(2\) つになったとき、適切に連結することで自分が勝つことにできそうです。つまり、勝敗判定基準 \(3\) における \(O\) の値を \(1\) 増やすか増やさないかを決めることができそうです。

これができないのは奇成分がいずれもひとつの頂点からなるときで、そうでなければ \(O\) の偶奇を好きに定めることができます。

勝敗判定基準 4-1

奇成分が \(2\) つであり、かつ少なくとも \(1\) つの奇成分は大きさが \(3\) 以上であるとき、先手必勝である。

この勝敗判定基準に上の補題を利用することができます。 つまり、奇成分が \(2\) つ かつ 少なくとも \(1\) つの奇成分の大きさを \(3\) 以上にするような手を打つことができないとして勝敗を判定しても結果が変わりません。

ここから、次のことが言えます。

勝敗判定基準 5-1

\(2\) 個の奇成分がどれもひとつの頂点からなるとき、それらの頂点を結び、新たな \(1\) 個の偶成分にしたのち勝敗判定基準 \(3\) を用いることで勝敗を判定することができる。

なぜなら、補題によるゲームの変更の結果、奇成分に対する合法手はそれをもうひとつの奇成分と結ぶただ一つの手のみになっており、最終的な \(|U|\) の偶奇は変えられないためです。

ここで、勝敗判定基準 4-1 の導出の際に勝敗判定基準 3 を使っていたところを勝敗判定基準 5-1 と入れ替えることで、次のことが言えます。

勝敗判定基準 4-2

奇成分が \(4\) つであり、かつ大きさが \(3\) 以上の奇成分が \(1\) つか \(2\) つであるとき、先手必勝である。

また、同様に勝敗判定基準 4-2 をもとに補題を適用することで、次のことが言えます。

勝敗判定基準 5-2

\(4\) 個の奇成分がどれもひとつの頂点からなるとき、それらを \(2\) つずつ組として互いの頂点を結び、新たな \(2\) 個の偶成分にしたのち勝敗判定基準 \(3\) を用いることで勝敗を判定することができる。

これを繰り返すことで、任意の正整数 \(n\) に対して次のことが言えます。

勝敗判定基準 4-n

奇成分が \(2n\) 個であり、かつ大きさが \(3\) 以上の奇成分が \(1\) つか \(2\) つであるとき、先手必勝である。

勝敗判定基準 5-n

\(2n\) 個の奇成分がどれもひとつの頂点からなるとき、それらを \(2\) つずつ組として互いの頂点を結び、新たな \(n\) 個の偶成分にしたのち勝敗判定基準 \(3\) を用いることで勝敗を判定することができる。

すべての正整数 \(n\) にわたる勝敗判定基準 4-n の主張をまとめて、より完結に記述することができます。

勝敗判定基準 4

大きさが \(3\) 以上の奇成分が \(1\) つか \(2\) つであるとき、先手必勝である。

勝敗判定基準 4 の結果に補題を適用して、大きさが \(3\) 以上の奇成分を \(1\) つか \(2\) つにするような手を打つことはできないとしておきます。

はじめに大きさが \(3\) 以上の奇成分が \(3\) つ以上ある場合に、終局時のグラフが満たすべき性質を考えます。

奇成分の個数を \(1\) 手で \(3\) つ以上減らすことはできないため、終局時にも奇成分は \(3\) つ以上存在します。

グラフに偶成分が存在するとき、それと他の連結成分をつなぐ合法手が存在するので、終局時のグラフには偶成分は存在しません。

また、グラフのある連結成分が完全二部グラフでないとき、連結性を変えないまま打てる合法手があるので、終局時のグラフの連結成分はすべて完全二部グラフです。

以上より、終局時のグラフのすべての連結成分は奇成分であり、かつ完全二部グラフになっています。 つまり、終局時のグラフの辺の本数は偶数本です。

よって次のことがわかります。

勝敗判定基準 6

大きさが \(3\) 以上の奇成分が \(3\) つ以上あるとき、

  • \(M\) が偶数なら、高橋くんが勝つ。
  • \(M\) が奇数なら、青木くんが勝つ。

ここまでの勝敗判定基準をまとめると、以下のようになります。

勝敗判定基準

  1. (勝敗判定基準 1)\(N\) が奇数のとき、
    • \(M\) が偶数なら、高橋くんが勝つ。
    • \(M\) が奇数なら、青木くんが勝つ。
  2. (勝敗判定基準 6)\(N\) が偶数で、大きさが \(3\) 以上の奇成分が \(3\) つ以上あるとき、
    • \(M\) が偶数なら、高橋くんが勝つ。
    • \(M\) が奇数なら、青木くんが勝つ。
  3. (勝敗判定基準 4)\(N\) が偶数で、大きさが \(3\) 以上の奇成分が \(1\) つか \(2\) つだけあるとき、先手必勝。
  4. (勝敗判定基準 3, 5-n)\(N\) が偶数で、大きさが \(3\) 以上の奇成分が存在しないとき、偶成分のうち極大な独立集合の大きさが奇数であるような連結成分の個数を \(O\) 、奇成分の個数を \(I\) として、
    • \(M+O+I/2\) が偶数なら、高橋くんが勝つ。
    • \(M+O+I/2\) が奇数なら、青木くんが勝つ。

これらを実装することで、この問題に正解することができます。

実装例は以下のようになります。

#include <iostream>
#include <vector>
#include <utility>
#include <algorithm>
#include <ranges>
#include <numeric>

int main() {
    using namespace std;
    unsigned N, M;
    cin >> N >> M;

    // 1. N が奇数のとき、M の偶奇から終局時の手番が確定する
    if (N & 1) {
        cout << (M & 1 ? "Aoki" : "Takahashi") << endl;
        return 0;
    }

    // N が偶数のとき

    vector<pair<unsigned, unsigned>> edges(M);

    for (auto&& [u, v] : edges) {
        cin >> u >> v;
        --u;
        --v;
    }

    // サイズが 3 以上の奇成分の個数を数える
    unsigned big_odd_component_count{};
    // 2O + I を数える
    unsigned odd_count{};

    // 頂点倍加して union-find
    vector<unsigned long> uf(2 * N), sz(2 * N, 1);
    ranges::fill(sz | views::take(N), 524289);
    iota(begin(uf), end(uf), 0U);
    const auto find{[&uf](auto i) { while (i != uf[i])i = uf[i] = uf[uf[i]]; return i; }};
    const auto unite{[&uf, &sz, find](auto i, auto j) {
        i = find(i);
        j = find(j);
        if (i == j) return;
        if (sz[i] < sz[j])swap(i, j);
        uf[j] = i;
        sz[i] += sz[j];
    }};

    for (const auto& [u, v] : edges) {
        unite(u, v + N);
        unite(u + N, v);
    }

    for (unsigned i{}; i < N; ++i)
        // 連結成分のうち、片方の極大な独立集合をみる
        if (i == find(i) && i < find(i + N)) {
            // 1 点からなるなら、I++
            if (sz[i] == 524289)
                ++odd_count;
            // 3 点以上からなる奇成分なら、それをカウント
            else if (sz[i] % 2) {
                // 2. 大きい奇成分が 3 つ以上なら、M の偶奇で(ルール変更後のゲームでの)終局時の手番が確定する
                if (++big_odd_component_count > 2) {
                    cout << (M & 1 ? "Aoki" : "Takahashi") << endl;
                    return 0;
                }
            }
            // 偶成分なら、極大な独立集合のサイズの 2 倍を足しておく
            else
                odd_count += sz[i] / 262144;
        }

    // 3. 大きい奇成分が 1 つか 2 つなら、先手必勝
    if (big_odd_component_count) {
        cout << "Aoki" << endl;
        return 0;
    }
    
    // 4. M + O + I/2 の偶奇で最終的な手番が確定する
    cout << ((M ^ odd_count / 2) & 1 ? "Aoki" : "Takahashi") << endl;

    return 0;
}

posted:
last update: