Acwing166 數獨題解 - DFS剪枝最佳化

Silly_3kidZ發表於2024-03-10

166. 數獨 - AcWing題庫

題意

數獨 是一種傳統益智遊戲,你需要把一個 9×9 的數獨補充完整,使得數獨中每行、每列、每個 3×3 的九宮格內數字 1∼9 均恰好出現一次。

請編寫一個程式填寫數獨。

思路

搜尋+剪枝(最佳化搜尋順序、位運算)

  • 最佳化搜尋順序:很明顯,我們肯定是從當前能填合法數字最少的位置開始填數字
  • 位運算:很明顯這裡面check判定很多,我們必須最佳化這個check,所以我們可以對於,每一行,每一列,每一個九宮格,都利用一個九位二進位制數儲存,當前還有哪些數字可以填寫.
  • lowbit:我們這道題目當前得需要用lowbit運算取出當前可以能填的數字.

code + 詳細註釋

#include <iostream>

#define lowbit(x) (x & -x) // lowbit操作
#define get(x, y) (row[x] & col[y] & cell[x / 3][y / 3]) // get(x, y) 找到該位置可以填哪些數的狀態

using namespace std;

const int N = 9, M = 1 << N;

int one[M], map[M]; // one[state]為該state中有幾個1, map[state]為state對應的十進位制值
int col[N], row[N], cell[3][3];
char str[100];

void init() { // 初始化(將所有位置都初始化可以填數的狀態)
    for (int i = 0; i < N; ++ i) row[i] = col[i] = (1 << N) - 1; 
    // 將行和列都用二進位制來最佳化(剛開始的位置都為1)

    for (int i = 0; i < 3; ++ i)
        for (int j = 0; j < 3; ++ j)
            cell[i][j] = (1 << N) - 1; // 每個3 * 3的小方格也用二進位制來最佳化(剛開始也都為1)
}

void draw(int x, int y, int t, bool is_set) { // 在(x, y)的位置上(is_set)<是/否>填t的操作 
    if (is_set) str[x * N + y] = '1' + t; // 如果填數的話, 將該數轉換為字元形式填入字串中對應的位置
    else str[x * N + y] = '.'; // 否則說明字串該位置上填的是'.';

    int v = 1 << t; // 找到該數對應二進位制之後的位置的數
    if (!is_set) v = -v; // 如果該位置不填數,則將該數取負

    row[x] -= v; //在這個原數對應的行減去該數的二進位制數
    col[y] -= v; // 在這個原數對應的列減去該數的二進位制數
    cell[x / 3][y / 3] -= v; // 在這個原數對應的小方格減去該數的二進位制數
}

bool dfs(int cnt) {
    if (!cnt) return true; // 知道沒有位置能填數就結束搜尋

    int minv = 10; // 記錄當前最少列舉方案
    int x, y; // x, y記錄列舉方案最少的位置的x, y

    for (int i = 0; i < N; ++ i)
        for (int j = 0; j < N; ++ j)
            if (str[i * N + j] == '.') { // 該位置對應的字串位置上為'.', 才說明能填數
                int state = get(i, j); // 找到該位置上能填的數的狀態
                if(one[state] < minv) { // 只有噹噹前位置的方案少於當前最少方案才有搜尋的必要
                    x = i, y = j;
                    minv = one[state];
                }
            }

    int state = get(x, y); // 找到最少列舉方案對應的位置的能填的數的狀態
    for (int i = state; i; i -= lowbit(i)) { // 列舉該位置上能填的數,用lowbit操作
        int t = map[lowbit(i)]; // 找到該位置上能填的數
        draw(x, y, t, true); // 填數
        if (dfs(cnt - 1)) return true; // 繼續搜尋
        draw(x, y, t, false); // 恢復
    }

    return false;
}

int main() {
    for (int i = 0; i < N; ++ i) map[1 << i] = i; // 預處理map[]

    for (int i = 0; i < 1 << N; ++ i)
        for (int j = 0; j < N; ++ j)
            one[i] += (i >> j & 1); // 預處理one[]

    while (cin >> str, str[0] != 'e') { // 多組輸入
        init(); // 初始化

        int cnt = 0; // 記錄有幾個空格需要填數
        for (int i = 0, k = 0; i < N; ++ i) 
            for(int j = 0; j < N; ++ j, ++ k) {
                if (str[k] != '.') { // 如果該位置已經有數了
                    int t = str[k] - '1'; // 找到該位置上的數
                    draw(i, j, t, true); // 在該位置上填上該數
                }
                else cnt ++ ; // 否則說明該位置需要填數
            }

        dfs(cnt); // 開始搜尋

        puts(str); // 輸出答案
    }

    return 0; // 結束快樂~
}

作者:Hustle
連結:https://www.acwing.com/solution/content/57159/
來源:AcWing
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

相關文章