G - Partial Xor Enumeration Editorial by MMNMM
この解説では線形代数の言葉を多く使います。 また、問題文中の 1-indexed の値を 0-indexed に読み替えています。
\(0\) 以上 \(2^{60}\) 未満の整数とそのビットごとの排他的論理和 \(\operatorname{xor}\) は、ベクトル空間 \({\mathbb{F}_2}^{60}\) の元とその加法であると考えることができます。
\(\mathbb{F}_2=\lbrace0,1\rbrace\) であることに注意すると、\(A=(A _ 0,\ldots,A _ {N-1})\) から正整数を \(1\) つ以上選んだ総 \(\operatorname{xor}\) は \(A\) の各元に対応するベクトルの列 \((\bm{a} _ i) _ {0\leq i\lt N}\) の線形結合 \(\displaystyle\sum _ {i=0} ^ {N-1}c _ i\bm{a} _ i\) で表されることがわかります。 逆に、このような線型結合で表されるベクトルはもとの列から \(1\) つ以上選んだ総 \(\operatorname{xor}\) と対応しています。
よって、 \((\bm{a} _ i) _ {0\leq i\lt N}\) が張る \({\mathbb{F}_2}^{60}\) の部分空間の基底 \((\bm{e} _ i) _ {0\leq i\lt x}\) をとることができれば、総 \(\operatorname{xor}\) と一対一に対応する表現が得られます。
特に、このような基底をとることができます。
\[ \left\lparen\begin{matrix}\bm e _ 0\\\vdots\\\bm e _ i\\\vdots\\\bm e _ {x-1}\end{matrix}\right\rparen=\left\lparen\ \begin{matrix}0&\cdots&0&0&\cdots&0&0&0&\cdots&0&1&\ast&\cdots&\ast\\\vdots&\ddots&\vdots&\vdots&\ddots&\vdots&\vdots&\vdots&\ddots&\vdots&\vdots&\vdots&\ddots&\vdots\\0&\cdots&0&0&\cdots&0&1&\ast&\cdots&\ast&0&\ast&\cdots&\ast\\\vdots&\ddots&\vdots&\vdots&\ddots&\vdots&\vdots&\vdots&\ddots&\vdots&\vdots&\vdots&\ddots&\vdots\\0&\cdots&1&\ast&\cdots&\ast&0&\ast&\cdots&\ast&0&\ast&\cdots&\ast\end{matrix}\ \right\rparen \]
対応する整数の言葉でいうと、次の \(2\) 条件を満たすような基底です。
- \(e _ 0\lt e _ 1\lt\cdots\lt e _ {x-1}\)
- \(e _ i\) の \(\operatorname{msb}\) (\(1\) である最上位ビット) が \(b\operatorname{bit}\) めであるとき、\(i\neq j\) について \(e _ j\) の \(b\operatorname{bit}\) めは \(0\)
このような基底は、いわゆる noshi基底 の構築の際に少し手を加えることで構築できます。
このような基底が取れたとき、総 \(\operatorname{xor}\) のうち小さいほうから \(i\) 番目の値 \(s _ i\) に対応するベクトル \(\bm s _ i\) は、\(i\) の二進法での表記 \(\displaystyle i=\sum _ {b=0} ^ {x-1}c _ i2 ^ i\ (c _ i=0,1)\) について \(\displaystyle\bm s _ i=\sum_{b=0}^{x-1}c _ i\bm e _ i\) と表せます。
よって、適当な \(X\) が与えられたとき \(s _ X\) の値は基底の本数 \(x\) について \(O(x)\) 時間で求められます。
長さ \(N\) の(ワードサイズに収まる)正整数の列に対して、基底をとるのは \(O(Nx)\) 時間で可能なので、基底を昇順にソートするのとあわせて、全体で \(O((N+(R-L))x)\) 時間でこの問題を解くことができます。 \(x\) は \({\mathbb{F}_2}^{60}\) の部分空間の基底の本数なので、\(0\leq x\leq 60\) となり、十分高速です。
基底の形をすこし変えることで、\(O(Nx+(R-L))\) 時間で解くこともできます。
実装例は以下のようになります。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
using namespace std;
int N;
cin >> N;
unsigned long L, R;
cin >> L >> R;
--L;
--R;
// 基底をつくる
vector<unsigned long> basis;
for (int i = 0; i < N; ++i) {
unsigned long A;
cin >> A;
for (auto b : basis)
if ((A ^ b) < A)
A ^= b;
for (auto &&b : basis)
if ((A ^ b) < b)
b ^= A;
if (A)
basis.emplace_back(A);
}
// 昇順に並べる
sort(begin(basis), end(basis));
// L 番目の値を得る
unsigned long ans{};
for (int i = 0; i < 60; ++i)
if (L >> i & 1)
ans ^= basis[i];
for (int i = 1; i < size(basis); ++i)
basis[i] ^= basis[i - 1];
// L 番目から R 番目までの値を出力
cout << ans;
for (unsigned long x{L}; x < R; ++x) {
ans ^= basis[__builtin_popcountl(x ^ (x + 1)) - 1]; // 差分を更新
cout << " " << ans;
}
cout << endl;
return 0;
}
posted:
last update: