《算法竞赛进阶指南》 0x22深度优先搜索 数独填数问题
题目链接:http://poj.org/problem?id=3074
本题的重点在于利用二进制位高效地判断一个位置能填哪些数,行、列、九宫格中能填的数的用一个二进制数表示,相与便是能填的数的二进制表示,使用lowbit函数可以
依次取出最低有效位进行搜索。基于几个常识:我们做数独的时候一定每次都是从可能性最大的点入手的,也就是行列和九宫格中都不冲突的数越少越好,所以可以每次搜索都将这样的位置找出来
进行处理,大大降低了搜索树的分支数量。
代码:
#include<iostream> #include<cstdio> using namespace std; char str[10][10]; int row[9],col[9],grid[9],cnt[1<<9+5],num[1<<9+5],tot; int g(int x,int y){//获取九宫格的编号 return ((x/3)*3)+(y/3); } void flip(int i,int j,int k){//翻转二进制位,行列编号以及要用来翻转的数 row[i]^= (1<<k); col[j]^=(1<<k); grid[g(i,j)]^=(1<<k); } bool dfs(int now){//参数表示的是当前还剩余没填的位置数量 if(now==0)return true; int tmp=10,x,y; //扫描所有可以填数的位置,计算出可能性最大的位置 for(int i=0;i<9;i++) for(int j=0;j<9;j++){ if(str[i][j]!='.')continue; int val=row[i]&col[j]&grid[g(i,j)];//能填的数的二进制位 if(cnt[val] < tmp){ tmp=cnt[val]; x=i; y=j; } } int val=row[x]&col[y]&grid[g(x,y)]; while(val){//扫描所有的有效位 int can = num[val&(-val)];//取出最低有效位,计算这个二进制位映射的能填的数 flip(x,y,can); str[x][y]='1'+can; if(dfs(now-1))return 1; flip(x,y,can);//回溯,还原状态 str[x][y]='.'; val-=val&(-val); } return false; } int main(){ for(int i=0;i<(1<<9);i++) for(int j=i;j;j-=(j&-j))cnt[i]++;//计算一种状态中有多少个可以填的数 for(int i=0;i<9;i++) num[1<<i]=i;//通过二进制位映射到一个可以填的数 char s[100]; while(scanf("%s",s) && s[0]!='e'){ tot=0; for(int i=0;i<9;i++) for(int j=0;j<9;j++)str[i][j]=s[i*9+j];//将一维数组变成二维数组 for(int i=0;i<9;i++)row[i]=col[i]=grid[i]=(1<<9)-1;//计算行列和九宫格中没有用过的状态 for(int i=0;i<9;i++) for(int j=0;j<9;j++){ if(str[i][j]!='.')flip(i,j,str[i][j]-'1'); else tot++;//搜索树的深度 } // cout<<"tot:"<<tot<<endl; dfs(tot); for(int i=0;i<9;i++) for(int j=0;j<9;j++)s[i*9+j]=str[i][j];//二维数组转成一维输出 puts(s); } }