显然要用到DFS,对于每个未填的空格,要排除掉同行同列同九宫内出现过的数字。不加优化不出意外的话会T飞︿( ̄︶ ̄)︿。
考虑剪枝:
- 优化搜索顺序
- 排除冗余信息
- 可行性剪枝(照当前分支这样走后面不可能达成)
- 最优性剪枝(照当前分支这样走的最优解都比当前解差)
- 记忆化搜索
这里我们主要考虑优化搜索顺序。这其实是跟大家做数独的时候的朴素感知是一样的,从可选的数少的空格开始试探。所以我们在每次搜索时都判断一下哪个格子的可选到数最小(可以预先处理出来可能会用到的所有二进制数里1的个数,用时直接调用)。
记录每个格子可选的数,可以用二进制状态压缩,开九位表示九个数。为了方便,我们把1~9映射成0 ~8。开三个数组 r o w [ N ] , c o l [ N ] , c e l l [ N / 3 ] [ N / 3 ] row[N],col[N],cell[N/3][N/3] row[N],col[N],cell[N/3][N/3]分别记录每行、每列、每个九宫格内有哪些数不能填。对于位置在 ( x , y ) (x,y) (x,y)的数,用 row[x] & col[y] & cell[x/3][y/3] 可以查到该空格可以填哪些数,那就老老实实搜索这些数吧233,可以用lowbit获得二进制下为1的每一位,将其转化成十进制数(1的位置对应的位数,可以预先处理出来)。搜索到每一个数时改变一下row.col.cell的状态,回溯的时候复原。
#include<bits/stdc++.h>
using namespace std;
const int N=9;
string str;
int mp[1<<N],nums[1<<N],row[N],col[N],cell[N][N];
inline int lowbit(int x)
{
return x&(-x);
}
int get(int x,int y)
{
return row[x]&col[y]&cell[x/3][y/3];//求出这一个位置可能会被填哪些数字
}
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;
}
bool dfs(int cnt)
{
if(cnt==0) return true;//所有待填数字都已经被填上
int minn=10,x,y;
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
{
if(str[i*9+j]=='.')
{
int temp=nums[get(i,j)];
if(temp<minn)
{
minn=temp;
x=i,y=j;
}
}
}//找到所有剩下的待填格中数字集合最小的一个,是一个剪枝过程
for(int i=get(x,y);i;i-=lowbit(i))//枚举这一格所有可能填的数字
{
int t=mp[lowbit(i)];
row[x]-=(1<<t);
col[y]-=(1<<t);
cell[x/3][y/3]-=(1<<t);
str[x*9+y]='1'+t;
if(dfs(cnt-1)) return true;
row[x]+=(1<<t);
col[y]+=(1<<t);
cell[x/3][y/3]+=(1<<t);
str[x*9+y]='.';//复原
}
return false;
}
int main()
{
for(int i=0;i<N;i++) mp[1<<i]=i;//预先存下二进制1所在的位置
for(int i=1;i<(1<<N);i++)
for(int j=i;j;j-=lowbit(j)) nums[i]++;
//预先存下每个之后可能会枚举到的二进制数内有几个1
while(cin>>str&&str[0]!='e')
{
init();
int cnt=0;
for(int i=0,k=0;i<9;i++)
for(int j=0;j<9;j++,k++)
{
if(str[k]!='.')
{
int t=str[k]-'1';//1~9映射到0~8
row[i]-=(1<<t);
col[j]-=(1<<t);
cell[i/3][j/3]-=(1<<t);
}//这一位数字是题给的,把它存进对应位置
else cnt++;//待填数字+1
}
dfs(cnt);
cout<<str<<endl;
}
return 0;
}