目录:算法日记
题目来源:166. 数独 - AcWing题库
题目描述
数独是一种传统益智游戏,你需要把一个 的数独补充完整,使得图中每行、每列、每个 的九宫格内数字 均恰好出现一次。
请编写一个程序填写数独。
输入格式
输入包含多组测试用例。
每个测试用例占一行,包含 个字符,代表数独的 个格内数据(顺序总体由上到下,同行由左到右)。
每个字符都是一个数字()或一个 .(表示尚未填充)。
您可以假设输入中的每个谜题都只有一个解决方案。
文件结尾处为包含单词 end 的单行,表示输入结束。
输出格式
每个测试用例,输出一行数据,代表填充完全后的数独。
输入样例
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
输出样例
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936
算法思路
若把每种数独状态当作一个结点,即可使用搜索进行求解。但考虑到每个空位上可能存在种情况,直接暴搜时间上必然爆炸,因此考虑剪枝优化。
- 优化搜索顺序:从可填合法数字最少的格子开始搜索;
- 排除等效冗余:每次都从可填合法数字最少的格子搜索,如果有多个格子可填合法数字相等,则搜索第一个格子,避免冗余;
- 可行性剪枝:行、列、九宫格内数字不能出现重复(此处使用位运算优化);
- 使用位二进制数表示数字出现情况,表示还没用,表示用过了;
- 判断某格子可填数字,只需要对行、列、九宫格的二进制数取交集,即进行与运算;
- 使用lowbit映射快速求出当前格子可用数字;
AC代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 9;
const int M = 1 << N;
int ones[M], map[M];
int row[N], col[N], cell[3][3];
char str[100];
//行列单元格判重初始化
void init() {
for(int i = 0; i < N; ++i) {
row[i] = col[i] = (1 << N) - 1;
}
for(int i = 0; i < 3; ++i) {
for(int j = 0; j < 3; ++j) {
cell[i][j] = (1 << N) - 1;
}
}
}
//绘制
void draw(int x, int y, int t, bool is_set) {
//修改字符串
if(is_set) str[x * N + y] = t + '1';
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;
}
int get(int x, int y) {
return row[x] & col[y] & cell[x / 3][y / 3];
}
int lowbit(int x) {
return x & (-x);
}
bool dfs(int cnt) {
if(!cnt) return true;
int minv = 10;
int 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(ones[state] < minv) {
minv = ones[state];
x = i, y = j;
}
}
}
}
int state = get(x, y);
for(int i = state; i; i -= lowbit(i)) {
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; //打表 快速判断最后一位1
for(int i = 0; i < M; ++i) {
for(int j = 0; j < N; ++j) {
ones[i] += i >> j & 1; //1的个数
}
}
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);
cout<<str<<endl;
}
return 0;
}