4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C++】bit全探索で高速探索をしよう

4
Posted at

競技プログラミングや、速度が求められる処理を書く場面では、すべての組み合わせを試したいという状況がよくあります。

配列やフラグを使って組み合わせを管理しようとすると、速度が遅くなります。

そこで役に立つのが、bit全探索のアルゴリズムです。

CPUはビット演算がとても得意で、二者択一のものをビットの各桁で表すことで、2^Nの組み合わせを最小限のコストで高速に列挙できます。

本記事ではC++によるbit全探索の基本の書き方と、高速探索が出来る理由について紹介します。

前提知識

  • C++の基礎構文を理解している
  • ビット演算について理解している

ビット演算の基本についてまだの方は、上記の記事を先に読むことを推奨します

bit全探索とは

bit全探索(bitmask enumeration)とは、N個要素の選ぶ/選ばないを、0/1のビットで表し、すべての組み合わせを列挙する技法のことです。

例えば、N = 3の場合を考えてみます。
各要素に対して各桁を割り当て、0の場合は選ばない1の場合は選ぶとして、表を作成してみます。

整数 2 進数 意味
0 000 何も選ばない
1 001 要素 0 を選ぶ
2 010 要素 1 を選ぶ
3 011 要素 0,1 を選ぶ
7 111 全部選ぶ

このように、ビットの各桁をbool値(選ぶ/選ばない)に見立てることで、整数一つで部分集合を表現することが出来るのです。

もちろん、bool値の配列で同じものを表現することも可能ですが、競技プログラミングなどの速度が求められる場面では、ビット演算が圧倒的に高速なために重宝されます。

サンプルコード

main.cpp
//---------------------------------------------------------------------
//! @file	main.cpp
//! @brief	ビットマスクを用いて部分集合を列挙するサンプルコード
//! @author	つきの
//---------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <bitset>
// エントリポイント
int main() {
	//---------------------------------------------------------------------
	// 要素数 N と要素の値を定義
	//---------------------------------------------------------------------
	constexpr int N = 3;
	//---------------------------------------------------------------------
	// 部分集合を列挙するための配列
	//---------------------------------------------------------------------
	std::vector<int> a = { 10, 20, 30 };

	//---------------------------------------------------------------------
	// 2^N 通りの部分集合を列挙
	//---------------------------------------------------------------------
	for (int mask = 0; mask < (1 << N); mask++) {
		//---------------------------------------------------------------------
		// 現在のビットマスク(整数と二進数)を表示
		//---------------------------------------------------------------------
		std::cout << "mask = " << mask
			<< "  bits = " << std::bitset<N>(mask) << "  (";
		//---------------------------------------------------------------------
		// 選ばれている要素を表示
		//---------------------------------------------------------------------
		for (int i = 0; i < N; i++) {
			// i 番目の要素が選ばれているかをビットマスクで判定
			if (mask & (1 << i)) {
				// i 番目の要素が選ばれている場合は値を表示
				std::cout << a[i] << " ";
			}
		}
		std::cout << ")\n";
	}
}
result
mask = 0  bits = 000  ()
mask = 1  bits = 001  (10 )
mask = 2  bits = 010  (20 )
mask = 3  bits = 011  (10 20 )
mask = 4  bits = 100  (30 )
mask = 5  bits = 101  (10 30 )
mask = 6  bits = 110  (20 30 )
mask = 7  bits = 111  (10 20 30 )

maskの回し方

まず、1 << Nは、1Nビット分左へずらすという意味になります。

N (10進数)1 << N 2進数(ビット列)
0 1 000001
1 2 000010
2 4 000100
3 8 001000
4 16 010000
5 32 100000
6 64 1 000000

以下のコードであれば

main.cpp
    for (int mask = 0; mask < (1 << N); mask++) {
        // 中略
	}

(1 << N)により、1をN分だけ左にずらした数、つまり2^Nまでループを回すという意味になります。

ビット判定

main.cpp
        //---------------------------------------------------------------------
		// 選ばれている要素を表示
		//---------------------------------------------------------------------
		for (int i = 0; i < N; i++) {
			// i 番目の要素が選ばれているかをビットマスクで判定
			if (mask & (1 << i)) {
				// i 番目の要素が選ばれている場合は値を表示
				std::cout << a[i] << " ";
			}
		}

まず、(1 << i)とは、i(iの最大値はN)分だけ1を左へずらす、要は右からi桁目のビットのみ1の値になった整数ということになります。

mask & (1 << i)は、&演算により、両方のビットが1である場合のみ1(true)になります。

(1 << i)は、右からi桁目のビット以外は0であるため、mask & (1 << i)全体でみると、maskの右からi番目のビットが1ならばtrue0ならばfalseということになります。

上記のようなアルゴリズムにより、int型の変数一つで、i番目の要素が選ばれているかの全組み合わせを探索することが出来ます。

bit全探索の限界

bit全探索は強力な手法ですが、計算量が2^N と指数的に増加するため、扱えるNには限界があります。

一般的には、N <= 20が約100万通りで、1秒以内で十分処理できる範囲であるため、実用的なラインとされています。

これを超える場合は、また条件によって違ったアルゴリズムを使用する必要があります。

総括

  • bit全探索のアルゴリズムを使用することで、高速全探索を実現することができる

  • 論理積を使うことで、各桁を各要素の組み合わせに見立てたビット判定を行うことが出来る

  • 速度の問題で、N <= 20前後が、一般的に実用的な範囲とされている

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?