题意
用A~P填写一个16*16的数独。
题解
DFS+超强剪枝
1、搜索每一个位置可以填的数,如果只有一个,立刻填上;如果没有可以填的数,立刻回溯。
2、枚举一个数字,在每个行\列\宫格中,有没有可以填的地方。如果只有一个,将其填上;如果无法填上,立刻回溯。
3、选取一个可能情况最少的格子,枚举其所有情况。
4、dfs(k+1),重复执行以上操作。
以上1和2的操作是有区别的:1只有当一个格子所处的行\列\宫格已经把除X外的数字填过时,才会将其填上,它们是直接影响到这个格子的;2是说除了这个格子外,(同一行\列\宫格中)其它的格子由于某种原因,都不能填X,只有这个格子可以填时,就可以填了。
对于每个位置可填的数,我们用一个二进制数来记录,这样能优化常数。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=20;
int map[maxn][maxn];
unsigned short table[maxn][maxn];//talbe[i][j]表示(i,j)可以填的数有哪些,0可填,1不可填
int filled;
void fill(int x,int y,int a)//(x,y)填a
{
filled++;
map[x][y]=a;
table[x][y]|=1<<a-1;//
for(int i=0;i<16;i++) table[x][i]|=1<<a-1,table[i][y]|=1<<a-1;
int r=x/4*4,c=y/4*4;
for(int i=0;i<4;i++)
for(int j=0;j<4;j++) table[r+i][c+j]|=1<<a-1;
}
int count_0(unsigned short x)//判断x中是否只有1个0|感谢来自china_no2的debug
{
x=~x;//取反,改为判断是否只有一个1
for(int i=0;x;i++)
{
if(x&1)
{
if(x>>1==0) return i;
return -1;
}
x>>=1;
}
return -1;
}
int hang(int x,int k)//第x行,数字k+1。返回>0表示唯一可填k+1的位置;返回-1表示出现超过1次;返回-2表示k+1没有可以填的位置
{
int p=-1;
for(int y=0;y<16;y++)
{
if(map[x][y]==k+1) return -1;//数字k+1已填
if(map[x][y]>0) continue;
if((table[x][y]&1<<k)==0)
{
if(p!=-1) return -1;//出现超过1次
p=y;
}
}
if(p!=-1) return p;
return -2;
}
int lie(int y,int k)//第y行,数字k
{
int p=-1;
for(int x=0;x<16;x++)
{
if(map[x][y]==k+1) return -1;
if(map[x][y]>0) continue;
if((table[x][y]&1<<k)==0)
{
if(p!=-1) return -1;
p=x;
}
}
if(p!=-1) return p;
return -2;
}
void gong(int r,int c,int k,int &x,int &y)//以(r,c)为左上角的宫格,数字k+1,(x,y)为唯一可填坐标
{
x=-2;
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
{
if(map[r+i][c+j]==k+1){x=-1;return ;}
if(map[r+i][c+j]>0) continue;
if((table[r+i][c+j]&1<<k)==0)
{
if(x!=-2){x=-1;return ;}
x=i;y=j;
}
}
}
int count_1(unsigned short x)//求x中1的个数
{
int re=0;
while(x)
{
if(x&1) re++;
x>>=1;
}
return re;
}
bool search()
{
if(filled==256) return true;
//只有一个数字可以填的格子先处理
for(int x=0;x<16;x++)
for(int y=0;y<16;y++)
{
if(map[x][y]>0) continue;
int k=count_0(table[x][y]);
if(k!=-1) fill(x,y,k+1);
}
//数字k+1在行\列\宫格中可填情况
for(int x=0;x<16;x++)
for(int k=0;k<16;k++)
{
int y=hang(x,k);
if(y==-2) return false;//回溯
if(y!=-1) fill(x,y,k+1);
}
for(int y=0;y<16;y++)
for(int k=0;k<16;k++)
{
int x=lie(y,k);
if(x==-2) return false;//回溯
if(x!=-1) fill(x,y,k+1);
}
for(int r=0;r<16;r+=4)
for(int c=0;c<16;c+=4)
for(int k=0;k<16;k++)
{
int x,y;
gong(r,c,k,x,y);
if(x==-2) return false;//回溯
if(x!=-1) fill(r+x,c+y,k+1);
}
if(filled==256) return true;
//备份
int t_filled;
int t_map[maxn][maxn];
unsigned short t_table[maxn][maxn];
t_filled=filled;
for(int i=0;i<16;i++)
for(int j=0;j<16;j++)
{
t_map[i][j]=map[i][j];
t_table[i][j]=table[i][j];
}
//找可能情况最少的格子来枚举
int mx,my,mn=16;
for(int i=0;i<16;i++)
for(int j=0;j<16;j++)
{
if(map[i][j]>0) continue;
int r=16-count_1(table[i][j]);
if(r<mn)
{
mn=r;
mx=i;my=j;
}
}
for(int k=0;k<16;k++)
{
if((table[mx][my]&1<<k)==0)
{
fill(mx,my,k+1);
if(search()) return true;
filled=t_filled;
for(int i=0;i<16;i++)
for(int j=0;j<16;j++)
{
map[i][j]=t_map[i][j];
table[i][j]=t_table[i][j];
}
}
}
return false;
}
char ar[maxn];
int main()
{
while(1)
{
filled=0;
memset(map,0,sizeof(map));
memset(table,0,sizeof(table));
for(int i=0;i<16;i++)
{
if(scanf("%s",ar)==EOF) return 0;
for(int j=0;j<16;j++)
{
if(ar[j]!='-') fill(i,j,ar[j]-'A'+1);
}
}
search();
for(int i=0;i<16;i++)
{
for(int j=0;j<16;j++) printf("%c",map[i][j]+'A'-1);
printf("\n");
}
printf("\n");
}
return 0;
}