DFS - POJ 2676 - Sudoku
给
定
一
个
9
×
9
的
方
阵
进
行
数
独
游
戏
。
给定一个9×9的方阵进行数独游戏。
给定一个9×9的方阵进行数独游戏。
要 求 每 一 行 , 每 一 列 , 每 一 个 小 九 宫 格 中 均 不 能 有 重 复 数 字 。 要求每一行,每一列,每一个小九宫格中均不能有重复数字。 要求每一行,每一列,每一个小九宫格中均不能有重复数字。
T 组 测 试 用 例 , 每 组 包 括 一 个 9 × 9 的 数 字 方 阵 , ′ 0 ′ 表 示 空 位 。 T组测试用例,每组包括一个9×9的数字方阵,'0'表示空位。 T组测试用例,每组包括一个9×9的数字方阵,′0′表示空位。
保 证 有 解 , 输 出 结 果 。 保证有解,输出结果。 保证有解,输出结果。
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来存储九个小九宫格的状态。 ①、用一个长度位9的二进制数表示当前行、当前列、当前位置所在的小九宫格的状态。′0′表示当前位的数字不能填,′1′表示可填。因此开长度为9的数组row和col来存储每一行的状态,3×3的方阵cell来存储九个小九宫格的状态。
② 、 数 组 o n e s 来 记 录 每 种 状 态 中 ′ 1 ′ 的 个 数 , 也 就 是 可 供 选 择 的 数 字 的 个 数 。 同 时 要 预 处 理 数 组 快 速 查 询 l o g 2 X , 来 查 看 具 体 是 哪 一 位 上 的 ′ 1 ′ , 也 就 是 填 了 哪 个 数 字 。 ②、数组ones来记录每种状态中'1'的个数,也就是可供选择的数字的个数。\\\qquad同时要预处理数组快速查询log_2X,来查看具体是哪一位上的'1',也就是填了哪个数字。 ②、数组ones来记录每种状态中′1′的个数,也就是可供选择的数字的个数。同时要预处理数组快速查询log2X,来查看具体是哪一位上的′1′,也就是填了哪个数字。
③ 、 d r a w 函 数 来 进 行 在 位 置 ( x , y ) 上 填 写 数 字 和 删 去 已 填 写 的 数 字 的 操 作 。 填 写 数 字 就 是 在 对 应 的 行 、 列 、 九 宫 格 的 状 态 所 对 应 的 位 置 填 ′ 0 ′ 。 删 去 这 个 数 就 是 在 该 位 填 ′ 1 ′ ③、draw函数来进行在位置(x,y)上填写数字和删去已填写的数字的操作。\\\qquad填写数字就是在对应的行、列、九宫格的状态所对应的位置填'0'。删去这个数就是在该位填'1' ③、draw函数来进行在位置(x,y)上填写数字和删去已填写的数字的操作。填写数字就是在对应的行、列、九宫格的状态所对应的位置填′0′。删去这个数就是在该位填′1′
④ 、 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;
}