---
# System prepended metadata

title: 枚舉
tags: [Competitive Programming]

---

# 枚舉
{%hackmd @themes/dracula %}

## 何謂枚舉
* 把所有可能性找出來
* 最慢、最直接的方法（暴力法）

> APCS p3, p4 的第一個子題通常能用枚舉偷分

## 常見方法
* 迴圈
* 遞迴
* 二進制

## 迴圈
* 就是全部跑一次

### 例題
#### 有幾個6
獨眼怪想知道 0~N 的數字裡面，共有幾個數字 6 在各個數字其中。

**INPUT**
輸入一個數字 N (N<=106)。

**OUTPUT**
輸出 0~N 中 6 的個數。

:::spoiler AC code
```cpp=
#include <iostream>
using namespace std;

int count_six(int n){
    int count = 0;
    while(n > 1){
        if(n % 10 == 6){
            count += 1;
        }
        n /= 10;
    }
    return count;
}

int main(){
    int n, s = 0;
    cin >> n;
    if(n < 6) cout << 0;
    else{
        for(int i = 6; i <= n; i++){
            s += count_six(i);
        }
        cout << s;
    }
}
```
:::

## 遞迴

### 例題
#### 獨眼怪試密碼
獨眼怪忘記自己保險庫的密碼了！
他只記得密碼共N個數字，且都介於0~M 之間。

**INPUT**
輸入只有一行 N, M (2<=N<=10, 1<=M<=9) 。

**OUTPUT**
請依照字典序輸出所有密碼可能讓獨眼怪參考。

:::spoiler AC code
```cpp=
#include <iostream>
using namespace std;

int n, m, ans[15];

void find(int pos){
    if(pos == n){
        for(int i = 0; i < n; i++) cout << ans[i];
        cout << "\n";
        return;
    }
    for(int i = 0; i <= m; i++){
        ans[pos] = i;
        find(pos+1);
    }
}

int main(){
    cin >> n >> m;
    find(0);
}
```
:::

## 二進制（子集合）

* 枚舉所有子集合的可能性
* 挑選東西 => 選 / 不選
* 分成兩種可能性，往下遞迴
* 複雜度：O(2^n)

### 例題
#### 分蘋果
有 N 顆蘋果裝在袋子裡，每顆蘋果都有甜美度，獨眼怪想要把它分成兩堆(其中一堆可以0顆)，使得這兩堆得蘋果甜美度相差最少。

**INPUT**
輸入有兩行，第一行為正整數 N(N≤20)，代表有幾顆蘋果。
第二行為 N 個整數代表甜美度，甜美度≤100。

**OUTPUT**
請輸出兩堆蘋果甜美度最少會差多少?

#### 想法
* 把全部的情形都舉出來就好了
:::spoiler AC code
```cpp=
#include <iostream>
using namespace std;

int n, a[25], s = 0, ans = 10000;

void comb(int sum1, int id){
    if(id == n){
        int sum2 = s - sum1;
        ans = min(ans, abs(sum1 - sum2));
        return;
    }
    comb(sum1 + a[id], id+1); // 拿
    comb(sum1, id+1); // 不拿
}

int main(){
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> a[i];
        s += a[i];
    }
    comb(0, 0);
    cout << ans;
}
```
:::

## 剪枝

### 何謂剪枝
* 一種想法
* 遞迴過程中，如果繼續遞迴下去也不可能是答案，那就直接`return`

## 例題

### p4. 數獨

輸入一個 9∗9 的數獨，如果數字是0代表還沒填入數字。
請找到字典序最小的解

**INPUT**
輸入有9行，每行有9個數字，數字之間用空格隔開

**OUTPUT**
輸出字典序最小的解，保證一定有解

#### 想法
* 終止條件：填到最後一格

:::spoiler AC code
```cpp=
#include <iostream>
using namespace std;

int board[9][9];

bool check(int x, int y, int num) { // 檢查

    for (int i = 0; i < 9; i++) { // 橫的和直的
        if (board[x][i] == num || board[i][y] == num) {
            return false;
        }
    }

    int startX = (x / 3) * 3;
    int startY = (y / 3) * 3;
    for (int i = startX; i < startX + 3; i++) { // 小正方形
        for (int j = startY; j < startY + 3; j++) {
            if (board[i][j] == num) {
                return false;
            }
        }
    }

    return true;
}

bool solve(int x, int y) {
    if (x == 9) {
        return true;
    }

    int nx = x, ny = y + 1;
    if (ny == 9) { // 檢查下一格有沒有要換行
        nx++;
        ny = 0;
    }

    if (board[x][y] != 0) { // 如果已經有答案就接下一個
        return solve(nx, ny);
    }

    for (int num = 1; num <= 9; num++) { // 每個數字都試過
        if (check(x, y, num)) {
            board[x][y] = num; // 填進去
            if (solve(nx, ny)) { // 如果這格這樣填下一格可以就一個遞迴安餒
                return true;
            }
            board[x][y] = 0; // 回復
        }
    }
    return false;
}

int main() {
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            cin >> board[i][j];
        }
    }

    if (solve(0, 0)) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                cout << board[i][j];
                if(j < 8) cout << " ";
            }
            cout << "\n";
        }
    }
}
```
:::

### p5. N-Queen

給你一個大小為 N∗N 的棋盤，你要在上面擺皇后
我們稱一個 N∗N 的盤面合法的條件如下:
1.對於任兩個皇后，橫、直、斜的不能相互連線 (詳情觀察範例測資)
2.共要擺放 N 個皇后

**INPUT**
輸入僅有一個正整數 N(≤10) 代表棋盤大小為 N∗N。

**OUTPUT**
請輸出所有合法盤面，並輸出解法數，依照字典序大小輸出，皇后為Q，空格為x。
輸出格式詳見範例輸出，所有合法盤面之後皆須接一個空行。

#### 想法
* 終止條件：擺滿了n個皇后

:::spoiler AC code
```cpp=
#include <iostream>
using namespace std;

int n, board[15][15], cnt = 0, solutions = 0;

bool check(int x, int y){
    for(int i = 0; i < n; i++){
        if(board[x][i] == 1 || board[i][y] == 1) return false;
    }
    for(int i = x, j = y; i >= 0 && j >= 0; i--, j--){
        if(board[i][j]) return false;
    }
    for(int i = x, j = y; i < n && j < n; i++, j++){
        if(board[i][j]) return false;
    }
    for(int i = x, j = y; i >= 0 && j < n; i--, j++){
        if(board[i][j]) return false;
    }
    for(int i = x, j = y; i < n && j >= 0; i++, j--){
        if(board[i][j]) return false;
    }
    return true;
}

void solve(int x){
    if(cnt == n){
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                if(board[i][j] == 1) cout << "Q";
                else cout << "x";
            }
            cout << "\n";
        }
        cout << "\n";
        solutions++;
    }
    for(int i = 0; i < n; i++){
        if(check(x, i)){
            board[x][i] = 1;
            cnt++;
            solve(x + 1);
            board[x][i] = 0;
            cnt--;
        }
    }
}

int main(){
    cin >> n;
    solve(0);
    cout << solutions;
}
```
:::

### p6. 字串MEX

給你一個僅由小寫英文字母構成的字串 S，你想知道這個字串的 MEX。
一個字串的 MEX 定義是:
1.最短且未出現在字串 S 當中的小寫英文字串。
2.如果有多個一樣短的小寫英文字串未出現在字串 S 請輸出字典序最小的。

**INPUT**
輸入第一行為一個整數 T(≤1000) 代表有 T(≤1000) 筆測資。
每筆測資有兩行，第一行為一個整數 N(N≤1000) 代表字串長度。
第二行為一個字串 S。

**OUTPUT**
請輸出其中一種解，結尾不要空格。

#### 想法
* 枚舉字串長度，由短到長

:::spoiler AC code
```cpp=
#include <iostream>
#include <string>
using namespace std;

int n;
string s;

bool check(string r){ // 剪枝
    int len = r.size();
    for(int i = 0; i < n-len+1; i++){
        if(r == s.substr(i, len)) return false;
    }
    return true;
}

bool search(int len, string now=""){
    if(len == int(now.length())){
        if(check(now)){
            cout << now << '\n';
            return true;
        }
        else return false;
    }
    for(char c = 'a'; c <= 'z'; c++){
        now += c;
        if(search(len, now)){
            return true;
        }
        now.pop_back();
    }
    return false;
}

void solve(){
    for(int i = 1; i <= n; i++){ // 枚舉長度 1~n 的字串
        if(search(i)){
            return;
        }
    }
}

int main(){
    int t;
    cin >> t;
    while(t--){
        cin >> n >> s;
        solve();
    }
}
```
:::

## 結語
### 解題過程
1. 確定枚舉對象、枚舉範圍和判定條件
2. 終止條件！！
3. 枚舉可能的解
4. 驗證，不合就剪掉