题目
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
思路
对于一个数独问题:
求一种填数方案, 使得结果满足:
- 每行1~9只出现一次
- 每列1~9只出现一次
- 每个格子1~9只出现一次
当枚举其中任意一个空位时, 我们需要知道当前能填哪些数, 即需要记录此时行状态, 列状态, 格子状态。他们都是有9种选择, 故可以用9位二进制数, 表示当前行, 列, 格子内可以选那那些数。
比如
col[i]
,i=000001010
, 则可以选2, 4两个数。 这样求三个条件的并集也很方便, 二进制与就可以。 可以先求出所有二进制对应的1的个数ones[N]
, 和 将lowbit
运算返回的1转换为个位数对应的map[N]
。
题目给的是一行输入, 也就是二维数组的一维形式, 即g[i][j]
的一维形式为g[i*m+j] m为行长度
。
搜索顺序为:
先随意选择一个空格子, 再枚举填某个数得到一个新的棋盘, 然后再进行之前的操作直到填满。
优化方法: 在选空格子时, 应该选择分支较少的, 也就是能选的数比较少的 怎么快速求出当前分支较少的空格子呢?没发, 直接暴力搜吧。
代码
const int N = 82, M = 9;
char g[N];
int row[M], col[M], cell[M][M];
int ones[1 << M], map[1 << M];
int cnt;
void draw(int x, int y, int t, bool isset)
{
if (isset)
g[x * 9 + y] = char(t + '1');
else
g[x * 9 + y] = '.';
int v = 1 << t;
if (isset)
v = -v;
row[x] += v;
col[y] += v;
cell[x / 3][y / 3] += v;
}
int lowbit(int x)
{
return x & -x;
}
int get(int x, int y) // x,y位置上能填那些数
{
return row[x] & col[y] & cell[x / 3][y / 3];
}
bool dfs(int cnt)
{
if (!cnt)
return true;
int x, y, minv = 10;
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
if (g[i * 9 + 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 v = map[lowbit(i)]; // 数
draw(x, y, v, true);
if (dfs(cnt - 1))
return true;
draw(x, y, v, false);
}
return false;
}
int main()
{
for (int i = 0; i < 9; i++)
map[1 << i] = i;
for (int i = 0; i < 1 << 9; i++)
for (int j = 0; j < 9; j++)
ones[i] += i >> j & 1;
while (cin >> g)
{
if (g[0] == 'e')
break;
// 初始化什么都能选
for (int i = 0; i < 9; i++)
row[i] = col[i] = (1 << M) - 1;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
cell[i][j] = (1 << M) - 1;
cnt = 0;
for (int i = 0, k = 0; i < 9; i++)
for (int j = 0; j < 9; j++, k++)
{
char c = g[k];
if (c != '.')
draw(i, j, c - '1', true);
else
cnt++;
}
dfs(cnt);
cout << g << endl;
}
return 0;
}