問題
長さ の整数列 と正の整数 が与えられる。 この数列 に対して、ある要素を だけ増やすという作業を最大 回行うことができる。
作業終了後に、 の各要素の bitwise AND を取ったときの最大値を求めよ。
ただし、 の値は全部で 個与えられるので、その 個の場合についての最大値をそれぞれ求めよ。
入力
最初の1行目には、整数 が与えられる。
次の1行には、整数 (ただし、 )が順番に与えられる。
その後 行にわたり、 行目には整数 が順に与えられる。(ただし、 )
条件
出力
行にわたり、 行目には のときの最大値を出力すること。
解法
この問題は2進数で表したときのANDの値を最大化する問題なので、2進数で表したときの桁数の大きい方から、ANDの値を1にできるかを考えます。
ここで、「数列 のすべての値に対してANDを取った値が の桁で になる」という状況を作るには、どのようにすればよいのかということを考えます。 すなわち、 \begin{align} ({a}_{1} \ \& \ {a}_{2} \ \& \ \cdots \ \& \ {a}_{n}) \ \& \ {2}^{j} = {2}^{j} \end{align} となるようにするために、 のそれぞれに を加える回数について考えます。
整数 は2進数で表したときに がどこか1つの桁にのみ出てくる整数であるので、 の値は または となります。 従って、
- のとき: を に加えることにより、 にできる
- のとき: に何も行わなくても、 が成立する
ということがそれぞれ言えるので、 であるすべての整数 に対して加えた値の総和が 以下であれば、最終的な値を にできると言えます。
このことから、 が大きい方から順に上記の作業を行い、 に対して足した値の合計が 以下になるようにしたときの最終的な値が答えとなります。
ここで、 であり、 であるので、 に対して行うことにより答えが得られます。
また、各 に対して合計 回の計算を行い、 の値は 個分与えられるので、この方法では の計算量で求められます。 この問題では であるので、実行時間制限にも十分間に合うと言えます。
ソースコード
整数列 と整数 が与えられている状態で、最大値を返す関数 solve()
を以下のように実装しました。
int n, q; ll a[int(1e5 + 5)], b[int(1e5 + 5)]; // b[i] は solve() の中で a[i] の値を一時的に保存するための配列 ll k; ll solve() { // 一次保存用の配列 b に配列 a の値をコピーする for (int i = 1; i <= n; i++) { b[i] = a[i]; } ll digit = pow(2, 60), cnt = 0, ans = 0; // digit は 2 ^ j の値を、cnt は現在までに1が足された回数を、ans は最終的な最大値をそれぞれ表す // digit の値が 0 より大きい間は以下のループを回す while (digit > 0) { ll now_cnt = 0; // now_cnt は現在の digit の値にするために必要な計算回数を表す for (int i = 1; i <= n; i++) { if ((b[i] & digit) > 0) { continue; // b[i] が digit の桁を含んでいれば、値を足す必要はない } now_cnt += digit - (b[i] % digit); // digit の桁を含んでいないときは、上記の数だけ足す必要がある if (now_cnt > k) { break; // そもそも、now_cnt が k を超えたら digit の値にはできないのでループから抜ける(オーバーフロー対策) } } if (now_cnt + cnt <= k) { // もし now_cnt と cnt の和が k 以下であれば、digit を答えに足すことができる cnt += now_cnt; // cnt の値の更新 ans += digit; // ans の値の更新 for (int i = 1; i <= n; i++) { if ((b[i] & digit) == 0) { b[i] += digit - (b[i] % digit); // b[i] の値の更新 } } } digit /= 2; // 次の桁を見るため、digit 自身を 2 で割る } return ans; }
感想
問題のタイトルの通り、この問題は easy version であり、hard version が 問題D2 として存在します。
hard version では、整数 の条件がより広くなっており( であり、積 についての条件はない)、今回の の解き方では太刀打ちできないようになっています。
多分 あたりの計算量で解く方法を探す必要がありそうですね。 気が向いたら考えてみます。