Official

D - Popcount and XOR Editorial by MMNMM


\(X,Y\) を \(2\) 進法で表したときの \(k\) 桁目 \((0\leq k\lt60)\) がそれぞれ \(i,j\ (i,j\in\lbrace0,1\rbrace)\) となるような \(k\) の個数を \(n _ {i,j}\) とします。

それぞれの条件から、以下のことがわかります。

  • \(\operatorname{popcount}(X)=a\iff n _ {1,0}+n _ {1,1}=a\)
  • \(\operatorname{popcount}(Y)=b\iff n _ {0,1}+n _ {1,1}=b\)
  • \(X\oplus Y=C\implies n _ {1,0}+n _ {0,1}=\operatorname{popcount}( C)\)

\(c\coloneqq\operatorname{popcount}( C)\) とします。

\(n _ {0,0}+n _ {0,1}+n _ {1,0}+n _ {1,1}=60\) なので、以上から各 \(n _ {i,j}\) の値は以下の式を満たす必要があります。

\[\begin{aligned} n _ {0,0}&=60-\dfrac{a+b+c}2\\ n _ {0,1}&=\dfrac{-a+b+c}2\\ n _ {1,0}&=\dfrac{a-b+c}2\\ n _ {1,1}&=\dfrac{a+b-c}2 \end{aligned}\]

これらは非負整数でなければならないため、

  • \(a+b+c\equiv1\pmod2\)
  • \(a+b+c\gt120\)
  • \(a\gt b+c,b\gt c+a,c\gt a+b\)

のいずれかが成り立っているとき、条件を満たすような \((X,Y)\) は存在しません。

これらがすべて成り立たないとき \(n _ {i,j}\) は非負整数です。 このとき、例えば以下のような操作によって実際に \((X,Y)\) を構築することができます。

\(S _ 0\) を \(n _ {0,0}\) 個の \((0,0)\) と \(n _ {1,1}\) 個の \((1,1)\) からなる列、\(S _ 1\) を \(n _ {0,1}\) 個の \((0,1)\) と \(n _ {1,0}\) 個の \((1,0)\) からなる列とする。
\(k=0,1,2,\ldots,59\) に対して順に次の操作を行う。

  • \(C\) の \(2 ^ k\) の位の値を \(b\) とし、\(S _ b\) の末尾から要素を取り出し、削除する。 取り出した要素を \((x,y)\)として、\(X,Y\) の \(2 ^ k\) の位の値をそれぞれ \(x,y\) とする。

実装例は以下のようになります。 以下の実装例では、列 \(S _ 0,S _ 1\) を明示的に持たず、\((0,0),(0,1),(1,0),(1,1)\) のそれぞれについて残っている個数のみを管理しています。 また、上のビットから計算を行うことで \(2\) 進法の各位の値から整数の値を求める処理を簡単にしています。

#include <iostream>
#include <bit>

int main() {
    using namespace std;
    unsigned a, b;
    unsigned long C;
    cin >> a >> b >> C;
    unsigned c = popcount(C);

    if((a + b + c) % 2 != 0 || a + b + c > 120 || a > b + c || b > c + a || c > a + b){
        cout << "-1" << endl;
        return 0;
    }

    // n?? = X, Y の k 桁目が ?? であるような k の個数
    unsigned n00{60 - (a + b + c) / 2}, n01{(-a + b + c) / 2}, n10{(a - b + c) / 2}, n11{(a + b - c) / 2};

    unsigned long X{}, Y{};
    for(unsigned B = 60; B--;){ // 上の bit から見る
        // 1 bit ずつ上に持ち上げて
        X *= 2;
        Y *= 2;

        if(1 & (C >> B)){ // C の B bit 目が 1 なら
            if(n10){ // (1, 0) が余っていれば
                ++X; // X の B bit 目を 1, Y の b bit 目を 0 に
                --n10; // 在庫を減らす
            }else{ // 余っていなければ
                ++Y; // Y のほうを 1 に
                --n01; // 在庫を減らしておく
            }
        }else{ // B bit 目が 0 なら
            if(n00){ // (0, 0) が余っていれば
                --n00; // どちらも 0 にし、在庫を減らす
            }else{ // 余っていなければ
                ++X; // どちらも 1 にし、
                ++Y;
                --n11; // 在庫を減らす
            }
        }
    }

    cout << X << " " << Y << endl;
    return 0;
}

posted:
last update: