Official

D - CTZ Editorial by mechanicalpenciI


\(\text{ctz}(n)\) の値は \(i=1,2,\ldots\) の順で次の操作を行うことで得る事ができます。

  1. \(n\)\(2\) 進表記した時の末尾から \(i\) 文字目を参照する。
  2. 0 ならば何も行わない。
  3. 1 ならば直ちに操作を終了する。この時の \(i\) の値から \(1\) を引いたものが \(\text{ctz}(n)\) の値である。

ここで、\(n\)\(2\) 進表記した時の末尾から \(i\) 文字目の数字(0 または 1)は \(n\)\((i-1)\) ビットだけ右シフトしたものと \(1\) の論理積 (AND) を取ることで得る事ができます。
\(n\leq 10^9<2^{30}\) であるため、上の操作は高々 \(30\) 回しか繰り返されず、十分高速にこの問題を解く事ができます。 よってこの問題を解く事ができました。
(参考: ビット演算について

他にも、あらかじめ整数型あるいは bool 型の配列に \(n\)\(2\) 進表記した時のビット列を保存しておく方法や、 \(n\)\(1\) ビットずつ右シフトしながら末尾を見る方法などもあります。いずれも十分高速です。


また、ビット列の操作に慣れていない場合は、それぞれの操作を整数に対しての操作に置き換えて解くこともできます。それぞれ、

  • \(n\)\(2\) 進表記した時の末尾の値 \(\to\) \(n\)\(2\) で割った余り
  • \(n\) に対する\(1\) ビットの右シフト演算操作 \(\to\) \(n\)\(2\) で割る除算操作 (小数点以下切り捨て)

に置き換える事ができるためこれらを組み合わせる事で同様の操作を実現できます。
今回の制約の下では十分高速であり、問題ありません。

中〜比較的高難易度の bitsetを用いた高速化などの定数倍高速化を求められる問題では高速なビット演算が必要となる事があるため、ビット演算に慣れておくと良いかもしれません。

c++ による実装例

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

int main() {
	int n;
	cin>>n;
	for(int i=0;;i++){
		if(n&(1<<i)){
			cout<<i<<endl;
			return 0;
		}
	}
}

Python による実装例

n=int(input())
for i in range(30):
    if n&(1<<i):
        print(i)
        break

posted:
last update: