Official

F - 出会いと別れ / Encounter and Farewell Editorial by tatyam


まずは、判定する部分だけを考えましょう。\(1\) 以上 \(N\) 未満の整数のうち、\(A\) に含まれないものの集合を \(B\) とします。
この問題は、「\((a\ \mathrm{xor}\ b) \in B\) となるような \((a, b)\) 間に辺を張った \(N\) 頂点のグラフが連結になるか?」という問題と見ることができます。
グラフが連結になるになるならば、 \(0\) から始めて、\(B\) の要素との \(\rm xor\) を取ることを繰り返して任意の \(x\ (0 ≤ x < N)\) に辿り着くことができます。
したがって、「\(B\) のいくつかの要素の \(\rm xor\) で任意の \(x\ (0 ≤ x < N)\) を表現できるか?」という問題に言い換えることができます。

これは、線形代数の言葉を使えば、「\(B\)\(i\) 番目の要素を \(2\) 進表記したときの \(2^j\) の位を \(b_{i,j}\) としたとき、\(\mathbb Z/2\mathbb Z\) 上の行列 \(X = \begin{pmatrix} b_{1,1} & \cdots & b_{1,n} \\ \vdots & \ddots & \vdots \\ b_{|B|,1} & \cdots & b_{|B|,n} \end{pmatrix}\)\(\rm rank\)\(n\) である」ということです。
\(X\)\(\rm rank\) は掃き出し法で \(O(n^2|B|)\) ( \(n \le{}\)(ワードサイズ) を仮定すれば \(O(n|B|)\) ) で求められるので、高速に判定できます。

構築部分を考えましょう。
\(X\) から線形独立な \(n\) 本の行ベクトル ( \(B\) から適切な \(n\) 個の要素 ) を取り出せれば、それ以外は必要ありません。
線形独立な行ベクトルの位置は掃き出し法をしたときにすでに求まっています。
線形独立な \(n\) 本の行ベクトルが求まれば、\(Nn/2\) 本の辺から適当な全域木を選んだり、グレイコード の順に辺を繋いだりすることで全域木を構築することができます。

線形代数をよく知らない人向け

\(1\) 以上 \(N\) 未満の整数のうち、\(A\) に含まれないものの集合を \(B = \{b_1, b_2, \dots, b_m\}\) とします。
\(B\) のいくつかの要素の \(\rm xor\) をとって作れる整数を考えます。

\(B = \{2^0, 2^1, \dots, 2^{n-1}\}\) ならば \(0\) から \(N-1\) までの全ての整数を作ることができます。
\(b_1, b_2, \dots, b_n\) の最上位 bit の位置が相異なっていても全て作れます。
これを作ることを目標にします。

\(B\) の要素に \(1\)\(\rm xor\) を取って \(B = \{b_1\text{ xor }b_i, b_2, \dots, b_m\}\ (i ≠ 1)\) のようにしても作れる整数は変わりません。
これを使うと、最上位 bit の位置が同じ要素が \(2\) つ存在したら、片方 \(\rm xor\) を取って最上位 bit を下げることができます。\(0\) が出てきたら \(B\) から取り除き、最終的に最上位 bit の位置が相異なる \(n\) 個の整数になったら全て作ることができます。
最終的な \(B\) の大きさが \(X\)\(\rm rank\) です。一般には \(2^{\text{rank}(X)}\) 個の整数を作ることができます。
あとは、\(0\) にならずに残った要素の元々の \(b_i\) を使うことで、全域木を構成できます。

回答例 (C++)

#include <iostream>
#include <vector>
using namespace std;
void chmin(int& a, int b){ if(a > b) a = b; }

int main(){
    int N, M;
    cin >> N >> M;
    vector ok(N, true);
    while(M--){
        int A;
        cin >> A;
        ok[A] = false;
    }
    vector<int> base, elim;
    for(int x = 1; x < N; x++) if(ok[x]){
        int y = x;
        for(int b : elim) chmin(y, y ^ b);
        if(y){
            base.push_back(x);
            elim.push_back(y);
        }
    }
    if(base.size() != __lg(N)) return puts("-1") & 0;
    int XOR = 0;
    for(int x = 1; x < N; x++){
        cout << XOR << ' ';
        XOR ^= base[__lg(x & -x)];
        cout << XOR << '\n';
    }
}

回答例 (Python)

N, M = map(int, input().split())
ok = [True] * N
for A in map(int, input().split()):
    ok[A] = False
base = []
elim = []
for x in range(1, N):
    if not ok[x]:
        continue
    y = x
    for b in elim:
        y = min(y, y ^ b)
    if y:
        base.append(x)
        elim.append(y)

def lg(x):
    return x.bit_length() - 1

if len(base) != lg(N):
    exit(print(-1))

xor = 0
for x in range(1, N):
    print(xor, end=' ')
    xor ^= base[lg(x & -x)]
    print(xor)

beet さんの提出 (C++)

posted:
last update: