ビット演算
読了目安 約3分
2 進数の桁を直接操作する。集合をビットで表し、bit 全探索で選び方を全列挙する。
この章の目次
コンピュータの中で、整数は 2 進数 (0 と 1 の並び) で表されています。 その桁を直接操作するのがビット演算です。 競プロでは「選ぶ・選ばない」の全パターン列挙に威力を発揮します。
2 進数リテラル
数値の先頭に 0b を付けると 2 進数で書けます。
String(x, radix: 2) で 2 進数の文字列に戻せます。
let x = 0b1010
print(x)
print(String(x, radix: 2))ビット演算子
| 演算子 | 意味 | 例 (1100 と 1010) |
|---|---|---|
& | AND: 両方 1 の桁だけ 1 | 1000 |
| | OR: どちらかが 1 なら 1 | 1110 |
^ | XOR: 片方だけ 1 なら 1 | 0110 |
~ | NOT: 全ビットを反転 | — |
<< | 左シフト: 桁を左へずらす | 1100 << 1 は 11000 |
>> | 右シフト: 桁を右へずらす | 1100 >> 2 は 11 |
let a = 0b1100
let b = 0b1010
print(String(a & b, radix: 2))
print(String(a | b, radix: 2))
print(String(a ^ b, radix: 2))
print(a << 1, a >> 2)<< 1 は 2 倍、>> 1 は 2 で割った商と同じです。
~は符号のビットも反転するため、Intでは結果が負になります。 実際の出番は、後述の「特定のビットを消す」形がほとんどです。
集合をビットで表す
n 個の要素それぞれの「入っている・いない」は、n ビットの整数 1 個で表せます。
i 番目の要素をビット 1 << i に対応させます。
この使い方はフラグ管理とも呼ばれます。
| 操作 | 書き方 |
|---|---|
| i 番目を追加する | bits |= 1 << i |
| i 番目を消す | bits &= ~(1 << i) |
| i 番目があるか | bits & (1 << i) != 0 |
var bits = 0
bits |= 1 << 0
bits |= 1 << 2
print(String(bits, radix: 2))
print(bits & (1 << 2) != 0)
print(bits & (1 << 1) != 0)bit 全探索
0 から 2^n − 1 までの整数は、n 個からの選び方すべてと 1 対 1 に対応します。 つまり、整数のループを回すだけで全パターンを列挙できます。 この手法を bit 全探索と呼びます。
let n = 3
for bits in 0..<(1 << n) {
var subset: [Int] = []
for i in 0..<n {
if bits & (1 << i) != 0 {
subset.append(i)
}
}
print(bits, subset)
}試してみよう: n を 4 に変えて、16 通りが列挙されることを確かめてください。
外側が 2^n 回、内側が n 回なので、計算量は O(2^n × n) です。 n が 20 くらいまでの「選ぶ・選ばない」問題なら、この型で全部調べられます。
学びどころ
| 概念 | 一言まとめ |
|---|---|
0b | 2 進数リテラル。String(x, radix: 2) で表示 |
& | ^ ~ | 桁ごとの AND、OR、XOR、NOT |
<< >> | 桁のシフト。2 倍と半分に対応 |
| 集合のビット表現 | i 番目の要素を 1 << i に対応させる |
| bit 全探索 | for bits in 0..<(1 << n) で選び方を全列挙 |
演習
1 行目に整数 N と X が空白区切りで入力されます。 2 行目に N 個の整数 v_1 … v_N が空白区切りで入力されます。
N 個から何個か選んで (1 個も選ばなくてもよい)、合計をちょうど X にできるなら Yes、できなければ No を出力してください。
制約: 1 ≤ N ≤ 10、1 ≤ v_i ≤ 1000、0 ≤ X ≤ 10000
入力例:
3 7
2 3 5
出力例:
Yes2 と 5 を選ぶと合計 7 になります。
模範解答
let first = readLine()!.split(separator: " ").map { Int($0)! }
let n = first[0]
let x = first[1]
let v = readLine()!.split(separator: " ").map { Int($0)! }
var found = false
for bits in 0..<(1 << n) {
var sum = 0
for i in 0..<n {
if bits & (1 << i) != 0 {
sum += v[i]
}
}
if sum == x {
found = true
}
}
if found {
print("Yes")
} else {
print("No")
}bits が「どの要素を選ぶか」を表し、2^N 通りすべての合計を調べます。
bits = 0 が「1 個も選ばない」に当たるので、X = 0 でも正しく Yes になります。