题目回顾
解题思路
刚看到这道题时,我想枚举所有区域的状况,但考虑到有16个区域,每个区域有4种状况,暴力枚举会有4^16(约4e9)种情况,显然会超时。
再仔细思考一下,这题和之前解谜游戏那题有相似之处,我们都是基于当前位置的状况,去枚举后续位置的状况,并在合法的情况下继续向后续位置搜索,如果各情况都不合法,便返回。有了这样的思路,就很容易想到要用dfs来实现。个人感觉bfs比dfs更依赖于图,而在这一题中,图是不容易建立的,这也是我们采用DFS来搜索的一个原因。
除此之外,对于整个数独图形,如何存储也是一个值得关注的问题。我选择的方式是开两个二维数组,用x[a][b], y[a][b]代表第a个区域,第b个数字的x、y坐标。这样存储的话会给我们之后旋转区域,以及检查某个状况是否合法带来便利。不过用其他存储方式貌似也可以,比如用v[a][b]代表第a个区域第b个位置的数字。
代码实现
本题整体思路容易想到,但是程序需要严格封装化,代码实现过程中还是有许多值得注意的地方,下面我将分块讲解:
前置代码和主函数
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
int x[16][16],y[16][16];//一维表示区域号,二维表示字母大小0~15
int min_times=48,cnt;//cnt记录总旋转次数,min_times记录cnt最小值
int times[16],TIMES[16];//times记录每次dfs每个区域旋转次数,TIMES记录最优的times
void dfs(int a);//a表示当前要旋转的区域
void rotate(int a);//逆时针旋转区域a
bool judge(int a);//判断操作后图形是否合法
int main()
{
int T;
scanf("%d",&T);
getchar();
while(T--)
{
min_times=48;
cnt=0;
memset(times,0,sizeof(times));
for(int i=0;i<16;++i)
{
for(int j=0;j<16;++j)
{
char c=getchar();
int num;//num表示0~F
if(c>='0'&&c<='9') num=c-'0';
else num=c-'A'+10;
x[i/4*4+j/4][num]=i%4;
y[i/4*4+j/4][num]=j%4;
}
getchar();
}
dfs(0);
printf("%d\n",min_times);
for(int i=0;i<16;++i)
{
for(int j=0;j<TIMES[i];++j)
{
printf("%d %d\n",i/4+1,i-i/4*4+1);
}
}
}
return 0;
}
- 由于有多组数据,每次输入前都要进行清空/初始化操作(养成好习惯);
- C++可直接使用bool类型,C语言需要include <stdbool.h>;
- 像这样输入复杂的题目,可以将样例输入包含到文件input.txt里(放在和程序一个路径下),在main函数开始时加上freopen("input.txt","r",stdin)便可实现程序自动从文档中读取数据。
DFS函数
void dfs(int a)//a表示当前要旋转的区域
{
if(a==16)
{
if(cnt<min_times)
{
min_times=cnt;
for(int i=0;i<16;++i) TIMES[i]=times[i];
}
return ;
}
for(int i=1;i<=4;i++)
{
rotate(a);
if(judge(a))//判断当前情况是否合法
{
cnt-=times[a];//注意cnt和times[a]如何变化
times[a]=i%4;
cnt+=times[a];
dfs(a+1);
}
}
return ;
}
- 经典的DFS结构,注意更新min_times时,也要更新TIMES数组(用于存储最优解);
- 注意time[a]和cnt的变化,不是每次都要加一,比如说旋转两次得到区域非法,旋转三次得到区域合法,那么每次都加一显然是错误的。
rotate函数
void rotate(int a)
{
for(int i=0;i<16;++i)
{
int temp=y[a][i];
y[a][i]=x[a][i];
x[a][i]=3-temp;
}
return ;
}
多写几个例子,便可发现旋转时坐标变化的规律。
judge函数
bool judge(int a)
{
for(int i=0;i<16;++i)//i表示0~F
{
bool flag_1[4]={false};
for(int j=a%4;j<=a;j+=4)//纵向检查
{
if(flag_1[y[j][i]]) return false;
else flag_1[y[j][i]]=true;
}
bool flag_2[4]={false};
for(int j=a/4*4;j<=a;++j)//横向检查
{
if(flag_2[x[j][i]]) return false;
else flag_2[x[j][i]]=true;
}
}
return true;
}
判断时只需判断a矩阵和其上面和左边的矩阵是否冲突即可。这里用flag数组标识在a矩阵的横向或纵向上某个数字的x或y坐标是否冲突。