题意
给定一个未填满的数独,要你找出它的解,数据保证了有唯一解。
分析
填数的顺序
想像一下平时人去解数独的时候,首先是找到最少需要填的某一列或者某一行,因为他给的信息比较多,我们再填的时候考虑的范围就会减少,举个栗子:
我们可以发现第5行代填的格子,只有一个,其他都是2个及以上,因此我们可以优先填这个待填范围较少的行/列,从而使其他的行列的状态不断减少。
填数的过程
在填数的过程中,我们要让当前数与当前所在的行、列和九宫格内都不重复,这里我们我们可以对于每行、每列、每个九宫格,分别用一个9位的2进制来表示,如果二进制的某一位是
1
1
1 表示可以使用该数,如果是
0
0
0 表示该数字不能使用。
再举个栗子:
上面这个二进制表示当前格子可以填9、6、4、2、1
到目前为止,我们有了二进制表示状态了,那么如何求这个数是否在它所在的行、列和九宫格出现勒?其实我们通过 & 运算就可以了,通过 & 算出他们的交集,为什么可以通过这样的操作就可以算出? 还是因为我们在定义每行、每列、每个九宫格的时候使用
1
1
1 表示可以用,而 & 运算只有在对应的所有位都为
1
1
1 的时候才返回
1
1
1 ,表示这个数在每行、每列、每个九宫格都可以使用,对于我们这个问题也就 痛 (愉) 苦 (快)的解决了~~~。
没懂???又举个栗子
表示当前格子可以填
2
2
2 和
4
4
4
代码片段如下
int get(int x,int y)
{
return row[x] & col[y] & cell[x/3][y/3];
// row 表示行,col表示列
// cell[3][3] 表示九宫格
}
怎么样是不是很简单,我们再把思路捋一捋:
1.先找到代填格子最少的一行或一列
2.通过 & 运算找到对应的可填的数
然后暴力出奇迹!!!
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 9,M = 1 << N;
int map[M],ones[M];
int row[N],col[N],cell[3][3];
char str[100];
// 初始化,全部初始化为 1 表示所有数均可用
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;
}
inline int lowbit(int x)
{
return x & -x; // 返回第一个二进制位为1的数
}
int get(int x,int y)
{
return row[x] & col[y] & cell[x/3][y/3];// 返回交集
}
// 这个函数表示填数的函数,is_set 为 true 的时候表示填,false表示不填
void draw(int x,int y,int t,bool is_set)
{
if(is_set) str[x * N + y] = '1' + t;
else str[x * N + y] = '.';
int v = 1 << t; // t 代填的数,1 << t 表示把 1 向右移动t位,即移动到对应的二进制为 1 的地方
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; // 记录最小的格子的位置
// 找最小格子的(整体理解)
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)) // 每次找到为二进制为1的数
{
int t = map[lowbit(i)]; // 相当于找到一个 log 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; // 预处理出所有的log 2
for(int i = 0; i < M; i ++) // 预处理每个数字,二进制中1的个数
for(int j = 0; j < N; j ++)
ones[i] += i >> j & 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); // 这里调用,是要更新row col cell 表示的状态, 因为当前有数,所有要将对应的位变成不可用
}
else cnt ++;
}
dfs(cnt);
cout << str << endl;
}
}