数独题解

原题链接

题意

  给定一个未填满的数独,要你找出它的解,数据保证了有唯一解。

分析
填数的顺序

  想像一下平时人去解数独的时候,首先是找到最少需要填的某一列或者某一行,因为他给的信息比较多,我们再填的时候考虑的范围就会减少,举个栗子:
在这里插入图片描述
我们可以发现第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;
        
    }
    
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值