ABC091 D問題 : Two Sequeneces

問題. Two Sequences

2つの長さ  n の非負数列  a = a_1, \ldots, a_n b = b_1, \ldots, b_n が与えられる. \oplus_{1 \le i, j \le n} (a_i + b_j) を求めよ.ただし, a_i \oplus b_j はビット単位の排他的論理和である.

制約 1 \le n \le 200,000,  0 \le a_i, b_i \le 2^{28}

解法1. 二分探索

解説 [1] をそのまま参考にした.

ビット単位の排他的論理和はビット単位で独立なので,ビット単位でビットが立っているかどうかを調べる.0-indexed として, k ビット目を調べる. T = 2^k とすると,周期  T k ビット目が立っているかどうかが分かる.つまり, [0, T), [2T, 3T), [4T, 5T), \cdots のいずれかに含まれる自然数 k ビット目が0で, [T, 2T), [3T, 4T), [6T, 7T), \cdots のいずれかに含まれる自然数 k ビット目が1である.したがって, a, b の各要素をそれぞれ自身の値の  2T による剰余に置き換えると,どの組の和も  0 \le a_i + b_j \le 4T を満たす.次に,  b を昇順にソートする. a_i を固定したときに,  a_i との和が  [T, 2T) または  [3T, 4T) に含まれる  b の要素数を数えると,そのパリティ (a_i + b_1) \oplus (a_i + b_2) \oplus \cdots \oplus (a_i + b_n) k ビット目のパリティが一致する.よって,各  a_i (1 \le i \le n) [T - a_i, 2T - a_i) または  [3T - a_i, 4T - a_i) に含まれる  b の要素数の総和のパリティが求める値の  k ビット目のパリティとなる.各ビット毎に  b のソートが支配的となるために計算時間は  O(n \log n) となる.また,答えとなるビット数の上限は  2 \times (2^{28} - 1) = 2^{29} - 2 となり高々30ビットでint型に収まる.しかし,各  k ビット目毎の区間に含まれる  b の要素数の総和の上限は  n^2 となるため int型に収まらないので注意が必要である.

計算時間 O(n \log n)

解法2. ループ展開

[2] で参照されているように,ループ展開やSIMDを使うと愚直な二重ループの  O(n^2) 時間解法が通る.

次をソースコードに埋め込むとループ展開のオプション -funroll-loops と最適化オプション -O3 が追加される.

#pragma GCC optimize ("-O3", "unroll-loops")

全体でTLEとなるが,  n = 100,000 のテストケースでTLEだったのが 1685 [ms] と通った.


[2] のループ展開を参考に高速化を行った.ポイントとしては,ループ展開されやすいように書き直すのと, int型から unsigned型に変更することである.unsigned型への変更によって  n = 100,000 のとき, 757 [ms] から 694 [ms] に少しだけの高速化となったが,これが TLE だった他のケースの AC に繋がった( n = 150,000 では 1545 [ms] から 1701 [ms]).下にACしたソースコードを載せる.ちなみに,bのブロックサイズを8192から4096に変更すると少し遅くなり,aのブロック化を無くすと TLE となった.
ループ展開の添字でブロックサイズが  2^k のとき,残った部分の開始時の添字は  n & ~ (2^k - 1) となる.これは, n から  n 2^k による剰余を引いた値となるので, n と同じブロックの先頭の要素と等しくなる(どこで参考にしたのか忘れてしまったが面白かったのでメモ).

まとめ

排他的論理和は独立性が重要と100回唱える.
 2 \times 10^{10} ぐらいが3秒で通るのは驚き.
SIMDの解法は時間があったときに [2] と [3] を参考に通してみる.