tekiheiの日記

競技プログラミングについて書きます

Codeforces 1285D Dr. Evil Underscores (R1800)

codeforces.com

問題

素数 nの数列 aが与えられます。 \underset{1 \leq i \leq n}{\max} (a_i \oplus X)としてあり得る値の最小値を求めてください。

制約
  •  1 \le n \le 10^{5}
  •  0 \le a_i \le 2^{30}-1
考察

 f(X)= \underset{1 \leq i \leq n}{\max} (a_i \oplus X)とします。 f(X)の先頭桁がなるべく小さくなるようにするのがよいので、 Xを先頭桁から決めていきます。 今 i桁目を見ているとして、次の3つの場合があります。

(1)  i桁目の数が全て 0のとき
 X i桁目を 0にすることで、 f(X) i桁目を 0にすることができます。

(2)  i桁目の数が全て 1のとき
 X i桁目を 1にすることで、 f(X) i桁目を 0にすることができます。

(3)  i桁目に 0 1の両方があるとき
 X i桁目を 0と1のどちらにしても、 f(X) i桁目は 1になります。 i桁目が 0であるような数の集合を S 1であるような数の集合を Tとします。 X i桁目を 0にした場合、 Sに含まれる数と Xとのxorが最大値になることはないので、次の桁を決めるときは Tだけを考えれば良いです。同様に X i桁目を 1にした場合は、 Sだけを考えれば良いです。両方試してよかった方を選びます。

(3)で両方試すと計算量が指数爆発する気がしますが、各数が見られる回数は桁数に等しいので、図を書くと各桁ごとに O(n)かかっていると分かるので、計算量は O(n \log \max a_i)です。

f:id:tekihei:20200126133147j:plain

#include<bits/stdc++.h>
using namespace std;

using Lint=long long;

int rec(int pos,vector<int> &a)
{
   if(pos<0) return 0;
   vector<int> S,T;
   for(auto I:a) (~I>>pos&1? S:T).push_back(I);
   if(S.empty()) return rec(pos-1,T);
   if(T.empty()) return rec(pos-1,S);
   return (1<<pos)+min(rec(pos-1,T),rec(pos-1,S));
}

int main()
{
   int N; cin>>N;
   vector<int> a(N);
   for(int i=0;i<N;i++) cin>>a[i];
   cout<<rec(29,a)<<endl;
}
感想

バチャ中には解けませんでした。この解法も頭を少しよぎりましたが、計算量が指数爆発すると思って除外してしまいました。シンプルな問題設定でとても好きです。