DFS - POJ 2676 - Sudoku

DFS - POJ 2676 - Sudoku

给 定 一 个 9 × 9 的 方 阵 进 行 数 独 游 戏 。 给定一个9×9的方阵进行数独游戏。 9×9
在这里插入图片描述

要 求 每 一 行 , 每 一 列 , 每 一 个 小 九 宫 格 中 均 不 能 有 重 复 数 字 。 要求每一行,每一列,每一个小九宫格中均不能有重复数字。

T 组 测 试 用 例 , 每 组 包 括 一 个 9 × 9 的 数 字 方 阵 , ′ 0 ′ 表 示 空 位 。 T组测试用例,每组包括一个9×9的数字方阵,'0'表示空位。 T9×90

保 证 有 解 , 输 出 结 果 。 保证有解,输出结果。

Sample Input

1
103000509
002109400
000704000
300502006
060000050
700803004
000401000
009205800
804000107

Sample Output

143628579
572139468
986754231
391542786
468917352
725863914
237481695
619275843
854396127

Time limit: 2000 ms
Memory limit: 65536 kB


分析:

暴 力 d f s 每 一 个 空 位 能 够 枚 举 的 数 字 , 找 到 一 组 可 行 解 就 返 回 。 暴力dfs每一个空位能够枚举的数字,找到一组可行解就返回。 dfs

剪 枝 方 案 : 剪枝方案:

① 、 优 化 搜 索 顺 序 : 优 先 搜 索 分 支 较 少 的 节 点 ( 能 够 选 择 的 数 字 较 少 的 空 位 ) 。 ①、优化搜索顺序:优先搜索分支较少的节点(能够选择的数字较少的空位)。 ()

② 、 可 行 性 剪 枝 : 行 、 列 、 小 九 宫 格 内 不 能 存 在 重 复 数 字 。 ②、可行性剪枝:行、列、小九宫格内不能存在重复数字。

具体落实:

① 、 用 一 个 长 度 位 9 的 二 进 制 数 表 示 当 前 行 、 当 前 列 、 当 前 位 置 所 在 的 小 九 宫 格 的 状 态 。 ′ 0 ′ 表 示 当 前 位 的 数 字 不 能 填 , ′ 1 ′ 表 示 可 填 。 因 此 开 长 度 为 9 的 数 组 r o w 和 c o l 来 存 储 每 一 行 的 状 态 , 3 × 3 的 方 阵 c e l l 来 存 储 九 个 小 九 宫 格 的 状 态 。 ①、 用一个长度位9的二进制数表示当前行、当前列、当前位置所在的小九宫格的状态。\\\qquad'0'表示当前位的数字不能填,'1'表示可填。\\\qquad因此开长度为9的数组row和col来存储每一行的状态,3×3的方阵cell来存储九个小九宫格的状态。 9019rowcol3×3cell

② 、 数 组 o n e s 来 记 录 每 种 状 态 中 ′ 1 ′ 的 个 数 , 也 就 是 可 供 选 择 的 数 字 的 个 数 。 同 时 要 预 处 理 数 组 快 速 查 询 l o g 2 X , 来 查 看 具 体 是 哪 一 位 上 的 ′ 1 ′ , 也 就 是 填 了 哪 个 数 字 。 ②、数组ones来记录每种状态中'1'的个数,也就是可供选择的数字的个数。\\\qquad同时要预处理数组快速查询log_2X,来查看具体是哪一位上的'1',也就是填了哪个数字。 ones1log2X1

③ 、 d r a w 函 数 来 进 行 在 位 置 ( x , y ) 上 填 写 数 字 和 删 去 已 填 写 的 数 字 的 操 作 。 填 写 数 字 就 是 在 对 应 的 行 、 列 、 九 宫 格 的 状 态 所 对 应 的 位 置 填 ′ 0 ′ 。 删 去 这 个 数 就 是 在 该 位 填 ′ 1 ′ ③、draw函数来进行在位置(x,y)上填写数字和删去已填写的数字的操作。\\\qquad填写数字就是在对应的行、列、九宫格的状态所对应的位置填'0'。删去这个数就是在该位填'1' draw(x,y)01

④ 、 g e t 函 数 用 来 求 位 置 ( x , y ) 能 够 填 的 数 的 状 态 。 等 于 行 、 列 、 九 宫 格 状 态 相 与 。 ④、get函数用来求位置(x,y)能够填的数的状态。等于行、列、九宫格状态相与。 get(x,y)

⑤ 、 每 次 操 作 我 们 先 查 找 所 有 空 位 中 , 可 供 选 择 的 数 字 的 个 数 最 少 的 空 位 的 位 置 。 计 算 这 个 位 置 的 状 态 , 接 着 枚 举 这 个 位 置 能 填 的 数 字 , 开 始 d f s 。 ⑤、每次操作我们先查找所有空位中,可供选择的数字的个数最少的空位的位置。\\\qquad计算这个位置的状态,接着枚举这个位置能填的数字,开始dfs。 dfs

代码:

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

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

int ones[M],log_2[M];
int row[N],col[N],cell[3][3];
char str[10][10];

void cal()
{
    for(int i=0;i<M;i++)
        for(int j=0;j<N;j++)
            ones[i]+=(i>>j&1);
    for(int i=0;i<N;i++) log_2[1<<i]=i;
}

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][y]='1'+t;
    else str[x][y]='0';
    
    int v=1<<t;
    if(!is_set) v=-v;  //删除第t位
    
    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 minv=10;  //计算能填的数的个数最少的位置
    int x,y;  
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
            if(str[i][j]=='0')
            {
                int st=get(i,j);
                if(ones[st]<minv)
                {
                    minv=ones[st];
                    x=i,y=j;
                }
            }
    
    int st=get(x,y);
    for(int i=st;i;i-=lowbit(i))
    {
        int t=log_2[lowbit(i)];
        draw(x,y,t,true);
        if(dfs(cnt-1)) return true;
        draw(x,y,t,false);   //回溯
    }
    
    return false;
}

int main()
{
    cal();
    
    int T;
    cin>>T;
    while(T--)
    {
        Init();
        for(int i=0;i<9;i++) cin>>str[i];
        
        int cnt=0;  //需要填的数的个数
        for(int i=0;i<N;i++)     //(i,j)是二维坐标,k是一维坐标
            for(int j=0;j<N;j++)
                if(str[i][j]!='0')
                {
                    int t=str[i][j]-'1';
                    draw(i,j,t,true);
                }
                else cnt++;
                
        dfs(cnt);
        
        for(int i=0;i<9;i++)
            printf("%s\n",str[i]);
    }
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值