POJ2676 (数独问题 + DLX + 状态优化顺序)
(1)最简单的最是去暴力DFS搜索答案 , 很容易想到 , 每行每列的方式去搜索 , 不过效率是真的不行;但这个还是给出代码 ,毕竟打了也不容易呀!
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=10;
int map[maxn][maxn];
bool row[maxn][maxn];
bool col[maxn][maxn];
bool grid[maxn][maxn];
bool dfs(int r,int c)
{
if(r==9) return true;//构造完毕
bool flag=false;
if(map[r][c])
{
if(c==8) flag=dfs(r+1,0);
else flag=dfs(r,c+1);
return flag;
}
int k=(r/3)*3+c/3;
for(int i=1;i<=9;i++)if(!row[r][i]&&!col[c][i]&&!grid[k][i])
{
row[r][i]=col[c][i]=grid[k][i]=true;
map[r][c]=i;
if(c==8) flag=dfs(r+1,0);
else flag=dfs(r,c+1);
if(flag) return true;
map[r][c]=0;
row[r][i]=col[c][i]=grid[k][i]=false;
}
return false;
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
memset(row,0,sizeof(row));
memset(col,0,sizeof(col));
memset(grid,0,sizeof(grid));
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
{
char x;
scanf(" %c",&x);
map[i][j]= x-'0';
if(map[i][j])
{
row[i][map[i][j]]=true;
col[j][map[i][j]]=true;
int k=(i/3)*3+j/3;
grid[k][map[i][j]]=true;
}
}
dfs(0,0);
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
printf("%d",map[i][j]);
printf("\n");
}
}
return 0;
}
(2)还有个更快的 ,就是利用舞蹈链(DLX)这种数据结构去快速实现这种问题 。(DLX:这里先给出模板,不先学习)
//******************************************************//
//输入T表示T组数据。 //
//每组数据为9个长度为9的字符串,空白处以字符0替代。 //
//POJ2676 //
//******************************************************//
#include <bits/stdc++.h>
using namespace std;
const int maxnode = 100010;
const int MaxM = 1010;
const int MaxN = 1010;
struct DLX{
int n, m, size; //行数,列数,总数
int U[maxnode], D[maxnode], R[maxnode], L[maxnode], Row[maxnode], Col[maxnode];
int H[MaxN], S[MaxM]; //S记录该列剩余1的个数,H表示该行最左端的1
int ansd, ans[MaxN];
void init(int _n, int _m){
n = _n;
m = _m;
for(int i = 0; i <= m; i++){
S[i] = 0;
U[i] = D[i] = i;
L[i] = i-1;
R[i] = i+1;
}
R[m] = 0; L[0] = m;
size = m;
memset(H, -1, sizeof(H));
}
void Link(int r, int c){
size++;
Col[size] = c, Row[size] = r;
S[c]++;
U[size] = U[c], D[size] = c;
D[U[c]] = size;
U[c] = size;
if (H[r] != -1) {
R[size] = H[r] ;
L[size] = L[H[r]] ;
R[L[size]] = size ;
L[R[size]] = size ;
}
else
H[r] = L[size] = R[size] = size ;
}
void remove(int c){//覆盖第c列。删除第c列及能覆盖到该列的行,防止重叠
L[R[c]] = L[c]; R[L[c]] = R[c];
for(int i = D[c]; i != c; i = D[i])
for(int j = R[i]; j != i; j = R[j]){
U[D[j]] = U[j];
D[U[j]] = D[j];
--S[Col[j]];
}
}
void resume(int c){
for(int i = U[c]; i != c; i = U[i])
for(int j = L[i]; j != i; j = L[j]){
++ S[Col[j]];
U[D[j]] = j;
D[U[j]] = j;
}
L[R[c]] = R[L[c]] = c;
}
//d为递归深度
bool dance(int d){
if(R[0] == 0){
ansd = d;
return true;
}
int c = R[0];
for(int i = R[0]; i != 0; i = R[i])
if(S[i] < S[c])
c = i;
remove(c);
for(int i = D[c];i != c;i = D[i]){
ans[d] = Row[i];
for(int j = R[i]; j != i; j = R[j]) remove(Col[j]);
if(dance(d+1)) return true;
for(int j = L[i]; j != i; j = L[j]) resume(Col[j]);
}
resume(c);
return false;
}
};
DLX g;
char s[15][15];
int main(){
int t;
scanf("%d", &t);
while(t--){
for(int i = 0; i < 9; i++)
scanf("%s", s[i]);
g.init(81*9, 81+81+81+81);
for(int i = 0; i < 9; i++)
for(int j = 0; j < 9; j++){
int x = i, y = j, z = x/3*3+y/3, w = i*9+j;
if(s[i][j] == '0'){
for(int k = 1; k <= 9; k++){
g.Link(w*9+k, w+1);
g.Link(w*9+k, 81+x*9+k);
g.Link(w*9+k, 162+y*9+k);
g.Link(w*9+k, 243+z*9+k);
}
}
else {
int t = s[i][j]-'0';
g.Link(w*9+t, w+1);
g.Link(w*9+t, 81+x*9+t);
g.Link(w*9+t, 162+y*9+t);
g.Link(w*9+t, 243+z*9+t);
}
}
g.dance(0);
for(int i = 0; i < g.ansd; i++){
int t = g.ans[i];
int a = (t-1)/9, b = (t-1)%9+'1';
s[a/9][a%9] = b;
}
for(int i = 0; i < 9; i++)
puts(s[i]);
}
return 0;
}
(3) 还有就是重要需要讲解的状态搜索:
1、答案
状态:我们关心数独每个位置填了什么数。我们需要在每个状态中找出没有填的位置,检查有哪些值可以填。这些可以填的值构成了向下递归的分支。(状态就是每个每个时刻的9*9数独)
搜索边界:1、所有位置填完了,找到答案。2、发现某个位置没有能填的值,搜索失败。
提醒:对于每个状态下,我们要找的是1个位置填什么,而不是枚举所有位置和能填的数(其他位置会在更深的地方被搜索到)。
2、剪枝
优化搜索顺序:如果是人类玩数独,策略一定是先填上“已经能够唯一确定的位置”,考虑在该位置填什么数,再向下搜索。所以我们在每个状态下选择所有未填的位置中,能填合法数字最少的位置,考虑在这上面填数。
3、位运算
对于每行,每列,每个九宫格,分别用一个9为二进制数保存哪些数字还可以填。
对于每个位置,把他锁在的行,列,九宫格对应的数取 & 运算就可以得到剩余哪些数可以填。lowbit(x)取出能填的数。
当某个位置的数填上时把对应的行,列,九宫格对应位置改为0。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
char str[10][10];
int row[10], col[10], grid[9], cnt[512], num[512], tot;
int g(int x, int y){
return ((x / 3) * 3) + (y / 3);//在第几个九宫格
}
void flip(int x, int y, int z){
row[x] ^= 1<<z; //x行z不能填了
col[y] ^= 1<<z;
grid[g(x,y)] ^= 1<<z;
}
bool dfs(int now){//剩余要填的数的总数
if(now == 0)return 1;
//优化顺序:枚举每个位置,找能填的最少的填
int t = 10, x, y;
for(int i = 0; i < 9; i++){
for(int j = 0; j < 9; j++){
if(str[i][j] != '0')continue;
int val = row[i] & col[j] & grid[g(i,j)];
if(!val)return 0;
if(cnt[val] < t){///查看那个小宫格填入好
t = cnt[val];
x = i, y = j;
}
}
}
//填数
int val = row[x] & col[y] & grid[g(x,y)];
for(; val; val -= val&-val){
int z = num[val&-val];//能填的数
str[x][y] = '1'+z;
flip(x,y,z);
if(dfs(now-1))return 1;
flip(x,y,z);
str[x][y] = '0';
}
return 0;
}
int main(){
//每个状态已经填的数有几个
for(int i = 0; i < 1<<9; i++)
for(int j = i; j; j-=j&-j)cnt[i]++;
//对于状态(1<<i),对应的数为'1'+num[1<<i];
for(int i = 0; i < 9; i++)
num[1<<i] = i;
int T; cin>>T;
while(T--){
//data in
for(int i = 0; i < 9; i++)scanf("%s",str[i]);
//初始化,都可以填。
for(int i = 0; i < 9; i++)row[i] = col[i] = grid[i] = (1<<9)-1;
tot = 0;
//
for(int i = 0; i < 9; i++)
for(int j = 0; j < 9; j++)
if(str[i][j] != '0')flip(i,j,str[i][j]-'1');
else tot++;
dfs(tot);
//data out
cout<<'\n';
for(int i = 0; i < 9; i++)
printf("%s\n",str[i]);
}
return 0;
}