tekiheiの日記

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

AGC015D A or...or B Problem(900)

atcoder.jp

問題

整数 A,Bが与えられます。 A以上 B以下の整数から 1個以上選んで、それらのbitwise orを取ってできる整数としてありうるものが何通りあるかを求めてください。

制約
  •  1 \le A \le B \lt 2^{60}
考察

 A=Bの場合は 1通りなので、 A \lt Bの場合を考えます。

 AとBが上位 k桁まで一致していたとします。 A以上 B以下の数の上位 k桁はこれと等しいので、bitwise orを取った結果の上位 k桁も必ずこれに一致します。よって上位 k桁は無かったことにしても問題ありません。

f:id:tekihei:20200130170228j:plain

上位 k桁を除くと、 A \lt Bより、 Aの最上位のビットは 0 Bの最上位のビットは 1です。

 A以上 B以下の整数を最上位のビットが 0のグループと、 1のグループに分けてみます。(1)最上位のビットが 0のグループから選ぶ場合、(2)最上位のビットが 1のグループから選ぶ場合、(3)両方から選ぶ場合、の3通りの場合を考えます。

f:id:tekihei:20200130193823j:plain

最上位のビットが 1で、それ以外のビットが 0の数を Xとすることにします。

(1)最上位のビットが 0のグループから選ぶ場合
bitwise orを取ることによって数が小さくなることはないので、最小値は Aです。また最上位のビットを立てることはできないので、最大値は X-1です。この間に含まれる数は全て作ることができます(その数自身を選べばよいです)。

(2)最上位のビットが 1のグループから選ぶ場合
最小値は Xです。すべての数を選んだときに最大になって、これは Bの上から 2番目の立っているビット以下を立てた整数です。これを Yとします。 X以上 Y以下の整数は全て作ることができます。なぜなら、 B Xの間に、最上位のビットと、( B 2番目に立っているビット以下の)あるビットのみが立っているような整数が存在するからです。 Xとこれらの整数を組み合わせることによって、 X以上 Y以下の全ての整数を作ることができます。

f:id:tekihei:20200130200323j:plain

(3)両方から選ぶ場合
まず最小値について考えます。bitwise orを取ることによって数が小さくなることはないので、各グループから一つずつ選ぶ場合を考えれば良いです。最上位のビットが 1のグループから選ぶべき整数は Xです。もう一方のグループから選ぶ数と Xの両方で立っているビットは存在しないので、これらのbitwise orは単純に和になります。よって最小値は X+Aだと分かります。また最大値はすべてのビットが立っている数で、これは 2X-1です。この間の整数は全て作ることができます( X Aを選ぶ、 X A+1を選ぶ、 \cdots\  X X-1を選ぶというようにすればよいです)。

以上より、作れる整数の区間は(1) [A,X-1]、(2) [X,Y]、(3) [X+A,2X-1]の3つであることが分かりました。あとはこれらの区間の和集合を求めれば良いです。(2)と(3)は交差するときがあるので、場合分けをするか、重複を引く必要があります。

#include<bits/stdc++.h>
using namespace std;
 
using lint=long long;
 
lint get(lint l,lint r){ return r-l+1; }
 
int main()
{
   lint A,B; cin>>A>>B;
   if(A==B){ cout<<1<<endl; return 0; }
 
   const int LOG=60;
   lint X=0,Y=0;
   for(int i=LOG-1;i>=0;i--){
      if((A>>i&1)==(B>>i&1)){
         if(A>>i&1){
            A-=1LL<<i;
            B-=1LL<<i;
         }
      }else{
         X=Y=1LL<<i;
         bool flag=false;
         for(int j=i-1;j>=0;j--){
            if(B>>j&1) flag=true;
            if(flag) Y|=1LL<<j;
         }
         break;
      }
   }
   lint ans=get(A,X-1);
   if(Y<X+A) ans+=get(X,Y)+get(X+A,2*X-1);
   else ans+=get(X,2*X-1);
   cout<<ans<<endl;
   return 0;
}
まとめ
  • bitwise orを取って小さくなることはない( a|b \ge a)
  • 数え上げで、答えが区間になる
感想

バチャ中に見たときは何をすればいいのか全然分かりませんでした。答えが区間になることが予想できたら、最小値と最大値は何か、その間の数を全部作れるという考察方法を学びました。「最上位のビットで二つのグループに分ける」という考えがどこから出てきたのかがよく分かりませんでしたが、(公式解説にあるように)bitwise orを取った結果が A以上になると分かったときに、「どこまでなら作れるか」ということを考えたら思いつけるのかなと思いました。