搜索:dfs + 剪枝:数独

题目链接:https://www.acwing.com/problem/content/168/

题目:

数独是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得图中每行、每列、每个 3×3的九宫格内数字 1∼9 均恰好出现一次。

请编写一个程序填写数独。

输入格式

输入包含多组测试用例。

每个测试用例占一行,包含 81个字符,代表数独的 81 个格内数据(顺序总体由上到下,同行由左到右)。

每个字符都是一个数字(1−9)或一个‘ .(表示尚未填充)。

您可以假设输入中的每个谜题都只有一个解决方案。

文件结尾处为包含单词 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

分析:

此题的话思路上就是,找到空缺的位置,然后对其这一行,这一列,周围圈出的9个值,看能上哪些数,然后将这些数,放入,看是否满足答案。

但是,这其中存在一些优化。

首先,可以对搜索顺序进行优化,我们每次都搜索可以选择的数少的情况,这样就减少了分支。

而对于可以选择哪些数进行搜索,这里可以使用二进制的方式进行优化。

 9位二进制数,111111111。

从右往左,第0位取1,则可以使用1. 第1位取1,则可以使用2,第2位取1则可以使用3.

以此类推。如果为0,则说明这个在某行或者某列,或9格中已经用到了,不能用。

而哪一位为1可以直接for()循环找,同样也可以使用lowbit()的方式找哪个位置为1.

举例:

n = 100100, 则 lowbit(n) = 100, ----> 1 << 2, 所以二进制中第2位(从0开始)为1.

n -= lowbit(n) ---> n == 1000000,以此类推。

而哪一位唯一,可以先进行预处理。 做一个lowbit()值 与 第几位为1的映射.

而从0~ 1 << 9 - 1 每个二进制表示的值中有多少个1,也可以先预处理出来。

代码实现:

# include <iostream>
using namespace std;

const int N = 10 , M = 1 << N;

int row[N],lie[N],ceil[3][3];

int num_1[M];  // num_1[i],当前i这个值1的个数
int map[M] ; // 由于lowbit(i)返回的是,2 ^ k 的值,而我们需要k,也就是第几个位置为1.所以预处理一个映射,2 ^ k ----> 就是 k , 也就是 map[2 ^ k] == k

char ch[100];

//第0个位置,则上1 ,  第1个位置,则上2,  第二个位置,则上3
int num[] = {1,2,3,4,5,6,7,8,9};

void init()
{
    for(int i = 0 ; i < 9 ; i++) // 直觉上感觉每次将一个一维变二维的时候,都会从下标为0开始,而不是下标为1开始
    {
        row[i] = lie[i] = ( 1 << 9 ) - 1;
    }

    for(int i = 0 ; i < 3 ; i++)
    {
        for(int j = 0 ; j < 3 ; j++)
        {
            ceil[i][j] = (1 << 9) - 1;
        }
    }
}

int lowbit(int x)
{
    return x & -x;
}

void draw(int x , int y , int t , bool state)  // 根据state的值,true则在[x][y]这个位置放上t这个值,false则删去这个值
{
    if(state)
    {
        ch[x * 9 + y] = '0' + t;
    }
    else
    {
        ch[x * 9 + y] = '.'; // 删除的话就变回原样
    }

    int v = 1 << ( t - 1 ) ;  // 第t - 1号位置为1,
    if(state)
    {
        row[x] -= v;
        lie[y] -= v;
        ceil[x / 3][y / 3] -= v;
    }
    else
    {
        row[x] += v;
        lie[y] += v;
        ceil[x / 3][y / 3] += v;
    }
}

int get(int x , int y)
{
    return row[x] & lie[y] & ceil[x / 3][y / 3];
}

bool dfs(int cnt)
{
    if(cnt == 0)
    {
        return true;
    }

    int temp = 10;
    int x,y;

    for(int i = 0 ; i < 9 ; i ++)  // 去找1最少的情况,也就是分支最少的情况
    {
        for(int j = 0 ; j < 9 ; j++)
        {
            if(ch[i * 9 + j] == '.')
            {
                if( num_1[ get(i,j) ] < temp )
                {
                    temp = num_1[ get(i,j) ] ;
                    x = i;
                    y = j;
                }
            }
        }
    } // 获得了x,y下1最少的情况

    int choose_num = get(x,y);  // x,y可以取哪些值
    for(int i = choose_num ; i ; i -= lowbit(i))
    {
        int tt = lowbit(i);
        draw(x , y ,  num[map[tt] ] , true);
        if(dfs(cnt - 1))
        {
            return true;
        }
        draw(x , y , num[map[tt] ]  , false);
    }

    return false;
}

int main()
{
    for(int i = 0 ; i < 1 << 9 ; i++)
    {
        int cnt = 0 ;
        for(int j = 0 ; j < 9 ; j++)
        {
            if(i >> j & 1)
            {
                cnt++;
            }
        }
        num_1[i] = cnt;
    }

    for(int i = 0 ; i < 9 ; i++)
    {
        map[1 << i] = i; // 1 << i就是我们后面会用到的lowbit()得到的值, 使用map[]对应一个我们真正需要的i,也就是第几个位置为1
    }

    while(cin >> ch)
    {

        if(ch[0] == 'e')
        {
            break;
        }

        init();

        int cnt = 0; // 统计空余出来需要填数值的个数
        for(int i = 0 ; i < 9 ; i++)
        {
            for(int j = 0 ; j < 9 ; j++)
            {
                if(ch[i * 9 + j] != '.')
                {
                    draw(i , j , ch[i * 9 + j] - '0' , true);
                }
                else
                {
                    cnt++;
                }
            }
        }

        dfs(cnt);

        puts(ch);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值