B - MissingNo. Editorial by MMNMM

空間計算量が小さい解法

(この解説は少し発展的な解説です。この問題の標準的な解説より少し難しい表現があります。)

時間計算量を \(O(N)\) としたままワードサイズの整数を定数個だけ使うようにしても、この問題を解くことができます(\(N\) および \(A _ i\) は \(1\) ワードに収まる大きさとし、\(N\) および \(A _ i\) の値を適当な変数に入力するのに extra \(O(1)\) 空間しか必要としないとします)。

ナオヒロくんが現在持っている整数の最大値と最小値がわかれば、もともと持っていた整数が何であるかがわかります。

もともと持っていた整数と、現在持っている整数に対する何らかの値がわかれば、なくした整数が何であるかわかる場合があります。 例えば、次のような値がわかるとなくした整数が何であるかわかります。

  • 積を素数 \(P(\gt A _ i)\) で割った余り
  • ビット単位 XOR

ここで、特にビット単位 XOR は \(1\) ワードに収まる大きさになります。 (積を素数 \(P\) で割った余りも \(O(N)\) 時間とワードサイズの整数定数個で計算することができますが、なくした整数を特定するために追加で \(O(N)\) 時間もしくは \(O(\log P)\) 時間が必要になります。また、和も \(O(N)\) 時間とワードサイズの整数定数個で計算することができます。)

(\(1\) ワードに収まる)整数 \(L,R\ (0\leq L\leq R)\) について、\(L\) 以上 \(R\) 未満の整数全体のビット単位 XOR は定数時間で求められます。

求め方

\(L\) 以上 \(R\) 未満の整数全体のビット単位 XOR を \(f(L,R)\) で表すこととします。

\(f(L,R)=f(0,L)\operatorname{xor}f(0,R)\) が成り立ちます(ビット単位 XOR が可換で結合的であることと、\(a\operatorname{xor}a=0\) および \(0\operatorname{xor}a=a\operatorname{xor}0=a\) であることを使えば示すことができます)。

また、非負整数 \(n\) について \(f(0,4n)=0\) が成り立つことも示せます(帰納法を使い、下 \(2\) ビットとそれ以外に分けることで示すことができます)。

よって、\(f(0,L)=f(0,L)\operatorname{xor}f(0,4\lfloor L/4\rfloor)=f(4\lfloor L/4\rfloor,L)\) が成り立ちます。 これはたかだか \(3\) つの整数の xor なので、定数時間で求められます。

よって、ナオヒロくんが持っていた整数全体のビット単位 XOR は、ナオヒロくんが持っていた整数の最小値と最大値がわかれば \(O(1)\) 時間で求めることができます。

ナオヒロくんが現在持っている整数全体のビット単位 XOR は \(O(N)\) 時間と extra \(O(1)\) 空間で求められるため、これらと併せて答えを得ることができました。

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

#include <iostream>
#include <algorithm>

using namespace std;

int main() {
    int N;
    cin >> N; // N を読み込む
    int min_A = 1000, max_A = 0, xor_A = 0, A; // A の最小値, 最大値, 総 xor
    while (N--) {
        cin >> A;
        min_A = min(A, min_A);
        max_A = max(A, max_A);
        xor_A ^= A;
    }

    ++max_A; // 半開区間にする
    while (max_A % 4 != 0)
        xor_A ^= --max_A; // 全体の xor から [0, max_A + 1) の xor を除く

    while (min_A % 4 != 0)
        xor_A ^= --min_A; // 全体の xor から [0, min_A) の xor を除く

    cout << xor_A << endl;

    return 0;
}

posted:
last update: