けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AOJ 1456 The Sparsest Number in Between (ICPC アジア 2024 B)

これ面白かった! ABC-E で出題されて水色 Diff などがあり得そうな雰囲気。

問題概要

正の整数  a, b が与えられる。

 a 以上  b 以下の整数のうち、popcount が最小のものを求めよ。複数ある場合は、そのうちの最小の整数を求めよ。

制約

  •  1 \le a \le b \le 10^{18}

考えたこと

まず最初に考えたことは、もし  a 以上  b 以下の整数であって、2 の冪乗であるものが存在したら、その popcount は 1 であり、 最小を達成するということだ。

そして、 a 以上  b 以下の整数であって、2 の冪乗であるものが存在するということは、 a, b を二進法で表したときの桁数が異なるということでもある。たとえば、二進法で表した時に  a = 101 b = 1101 のように桁数が異なるとき、 a, a+1, a+2, \dots と順に見ていって桁数が上がる瞬間(この場合は  1000 )に、2 の冪乗になっているのだ。

よって、 a 以上  b 以下の整数であって、2 の冪乗であるものが存在しないのは、二進法で表したときの  a, b の桁数が等しい場合だと言える。

ここまで考えると、 a, b を二進法で表して考えると良いと考えられる。

二進法で考える

以降、 a, b を二進法で表すことにする。 a, b を左から見ていったとき、共通の部分は無視してもよい。たとえば、

  •  b = 10110011
  •  a = 10100111

としよう。このとき、左から最初の "101" は共通している。 a 以上  b 以下の整数はすべて、左から 3 桁が "101" であることに変わりない。よって、これを除外して考えて、あとから "101" を付け足すことにしよう。

そうすると、

  •  b = 10011
  •  a = 00111

についての問題になる。このとき、最上位の桁の値が、 a の方は 0 で  b の方は 1 であることに注意しよう。

 a の最上位が 0 で  b の最上位が 1 の場合

こうして問題は、 a の最上位が 0 で  b の最上位が 1 であるような問題に帰着された。まず  a = 0 のときは、答えは 0 なので、 a \ge 1 としよう。

このとき、先の考察によって、popcount が最小であるものは、最上位が 1 で残りが 0 であるものだとわかる。ただし、これが popcount 最小であるもののうちの最小とは限らない。実際、上の例

  •  b = 10011
  •  a = 00111

において、答えは

 01000

となる。具体的には、 a の最左の桁を  k として  2^{k} が答えとなる。

コード

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

int main() {
    long long a, b, res = 0;
    cin >> a >> b;

    for (int d = 61; d >= 0; d--) {
        bool a_flag = (a >> d) & 1;
        bool b_flag = (b >> d) & 1;

        if (a_flag && b_flag) {
            res += 1LL<<d;
            a -= 1LL<<d, b -= 1LL<<d;
        } else if (b_flag) {
            if (__builtin_popcountll(a) <= 1) res += a;
            else {
                int k = d;
                while (k >= 0 && !((a >> k) & 1)) k--;
                res += 1LL<<(k+1);
            }
            break;
        }
    }
    cout << res << endl;
}