Pythonで解く【初中級者が解くべき過去問精選 100 問】(010 - 014 全探索：ビット全探索)

# 1. 目的
[初中級者が解くべき過去問精選 100 問](https://qiita.com/e869120/items/eb50fdaece12be418faa)をPythonで解きます。
すべて解き終わるころに水色になっていることが目標です。

本記事は「010 - 014 全探索：ビット全探索」です。

# 2. 総括
今まではbit全探索の際は```itertools.product```で```(0, 1)```を生成していましたが、後々のためにbitシフトでも解いてみました。


# 3. 本編

## 010 - 014 全探索：ビット全探索

### 010. [ALDS_5_A - 総当たり](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_5_A&lang=ja)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668985/ff12084a-8e7c-4aaf-bdc7-93455218256e.png)

#### 回答
```python

n = int(input())
A = list(map(int, input().split()))
q = int(input())
M = list(map(int, input().split()))

check_list = [0] * (2000 * 20 + 1)
for i in range(2**n):
    total = 0
    for j in range(n):
        if (i >> j) & 1:
            total += A[j]
    check_list[total] = 1

for m in M:
    if check_list[m]:
        print('yes')
    else:
        print('no')

```
リスト```A```のすべての要素について「使う」「使わない」を判定し、その合計がリスト```M```に含まれるか否かを判定します。
最初は下記のようなコードとしましたが3重のforループとなり間に合いません。
算定した```total```を毎回```m```と比較すると速度が遅くなるため、算定した```total```を添え字とする```check_list```にフラグをたて、そのあとに改めて添え字```m```をチェックする必要があります。

```python
# これだと間に合わない
n = int(input())
A = list(map(int, input().split()))
q = int(input())
M = list(map(int, input().split()))
for m in M:
    ans = 'no'
    for i in range(2**n):
        total = 0
        for j in range(n):
            if (i >> j) & 1:
                total += A[j]
        if total == m:
            ans = 'yes'
            break
    print(ans)

```


---

### 011. [AtCoder Beginner Contest 128 C - Switches](https://atcoder.jp/contests/abc128/tasks/abc128_c)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668985/4b81c23c-706d-30cc-1e2c-2b31a049ca13.png)

#### 回答
```python

N, M = map(int, input().split()) # Nはスイッチの数、Mは電球の数
lights = [[0] * N for _ in range(M)]
for i in range(M): 
    temp = list(map(int, input().split())) # 0番目はスイッチの個数、1番目以降はスイッチを示す
    k = temp[0]
    switches = temp[1:]
    for j in range(k):
        lights[i][switches[j]-1] = 1
P = list(map(int, input().split())) # 個数を2で割ったあまりが要素と等しい場合に点灯する

answer_count = 0
for i in range(2**N): # bit探索
    flag = True
    for k in range(M): # すべての電球について調べる
        count = 0
        for j in range(N): # bitで1フラグがある番号について行う
            if (i >> j) & 1:
                count += lights[k][j]
        if count % 2 != P[k]: # 1個の電球でもpと一致しなかった場合はflagをFalseにしてbreak
            flag = False
            break
    if flag: # すべてクリアしてflagがTrueの場合にcountを増やす
        answer_count += 1

print(answer_count)

```
問題文を理解するのが若干難しいです。
入力の与えられ方が扱いずらいので、入力の受け取り方を工夫し、縦軸を電球、横軸をスイッチの2次元配列を作成しました。この配列では、それぞれの電球とスイッチがつながっている場合は1、つながっていない場合は0とします。

この配列がつくれればbit探索が容易にできます。
方針は、
1. 上記記載のように電球とスイッチの2次元配列を作成
2. すべてのスイッチについて、onとするかoffとするかをbit全探索（for文のiとjで実現）
3. それぞれのbitの状態で、すべての電球が点灯するか否かをチェック（for文のkで実現）
4. 電球が点灯するか否かのチェックの際は```flag```で情報を持って置き、```flag```が```False```となった段階で```break```する
5. ```flag```が```True```となる状態数を数えあげてそれが答え
です。



---

### 012. [AtCoder Beginner Contest 002 D - 派閥](https://atcoder.jp/contests/abc002/tasks/abc002_4)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668985/9926999e-2fad-66d5-788a-4d0aed7f749a.png)

#### 回答
```python

import itertools

N, M = map(int, input().split()) # Nは議員の数、Mは人間関係の数
members = list(range(N))
relation = [[0] * N for _ in range(N)] # 人間関係のマトリクス
for _ in range(M):
    x, y = map(int, input().split())
    x -= 1
    y -= 1
    relation[x][y] = 1
    relation[y][x] = 1

max_answer = 0
for group_num in range(1, N+1): # グループの人数をすべて試す
    temp_answer = 0
    for c in itertools.combinations(members, group_num): # memberがら特定の人数を列挙する
        flag = True
        for base_c in c: # 人間関係をチェックする人
            for check_c in c: # チェック対象となるメンバー
                if base_c == check_c: # 自分自身はチェックしない
                    continue
                if relation[base_c][check_c] == 0: # 人間関係がない場合はすぐにbreak
                    flag = False
                    break
            if not flag:
                break
        if flag: # flagがTrueのときだけOK
            temp_answer = group_num
    max_answer = max(max_answer, temp_answer)

print(max_answer)

```


```itertools``` を使わないでできなかったのでやむを得なく```itertools.combinations```を使用しました。
コードがやや複雑になりましたがやっていることは下記の通り単純です。

1. ```N```人の議員から抽出する任意の人数```group_num``を指定する。これを1~N人まで全通りやります
2. ```N```人の```members```から```group_num```人を抽出する場合分けをすべて列挙します
3. 各議員(```base_c```)について、上記2で抽出されたメンバー(```check_c```)と知り合いか否かをチェックします
4. チェックの際は自分自身をチェックしないように気を付け、知り合い出なかった場合はすぐに```break```することとします
5. すべての議員(```base_c```)で知り合い関係があると判断された場合(```flag==True```)はその```group_num```が答えの候補です
6. 1~6を繰り返し```group_num```の最大値が答え

です。

---

### 013. [JOI 2008 予選 5 - おせんべい](https://atcoder.jp/contests/joi2008yo/tasks/joi2008yo_e)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668985/bcaefccc-f6dd-7100-e6b7-4e84fde37073.png)

#### 回答
```python
import numpy as np

R, C = map(int, input().split()) # R:縦、C:横
grid = np.array([list(map(int, input().split())) for _ in range(R)])

max_answer = 0
for i in range(2**R):
    copy_grid = grid.copy() # 行列を書き換えるのでコピーを取っておく
    for row in range(R):
        if (i >> row) & 1: # 数の少ない行方向についてbit探索を行う
            copy_grid[row, :] = copy_grid[row, :] ^ 1 # bit演算で0,1を反転

    col_sum = copy_grid.sum(axis=0) # 各列の合計を求めておく
    for col in range(C):
        if  col_sum[col] >= R // 2 + 1: # 1の数が多い箇所はひっくり返す
            copy_grid[:, col] = copy_grid[:, col] ^ 1
    
    answer = R * C - copy_grid.sum()

    max_answer = max(max_answer, answer)

print(max_answer)

```
ヒントにもある通り、Rの上限が小さいので、Rを全探索を行い、C側で微調整を行います。
方針は、
1. R方向で裏返す箇所を全通り試す
2. R方向で裏返した後にC方向で微調整として、1となっている列が多い箇所だけ裏返す
3. 出来上がった行列の0の数を数える(全体のマス目 - 合計)
4. それぞれの数えた結果の最大をとったものが答え

です。
裏返す操作である0を1、1を0に変換する方法は、bit演算で```^1```を使います。


---

### 014. [Square869120Contest #4 B - Buildings are Colorful!](https://atcoder.jp/contests/s8pc-4/tasks/s8pc_4_b)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668985/c2a557ce-6125-873a-083e-ac93efa98d34.png)

#### 回答
```python

import itertools

N, K = map(int, input().split()) # Nは建物の数、K色以上に塗る
A = list(map(int, input().split()))
N_index = list(range(1,N)) # 一番左は固定なので

min_cost = float('inf')
for target_indexes in itertools.combinations(N_index, K-1): # N_indexからK-1個選ぶ方法を全通り試す
    cost = 0
    copy_A = A.copy()
    for i in target_indexes:
        if copy_A[i] <= max(copy_A[:i]):
            diff = max(copy_A[:i]) - copy_A[i] # ターゲットの建物の場合はそれより左側の建物のmaxより1だけ大きくする
            copy_A[i] += diff + 1
            cost += diff + 1

    min_cost = min(min_cost, cost)

print(min_cost)

```

方針は、
1. 一番左の建物は固定
2. 2番目の建物以降から```K-1```を選ぶのを全通り試してみる
3. 選んだ建物のindexを```target_indexes ```に格納し、各建物の高さを調べる
4. 建物```i```がそれ以前の建物のmaxの高さ+1になるように、建物```i```の高さを調整し、調整した分を```cost```として集計
5. 上記を全通りやったあとに```cost```の最小値をとったものが答え

です。

---