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
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。