带旋转的数独游戏——DFS的典型应用

题目回顾

 

 解题思路

        刚看到这道题时,我想枚举所有区域的状况,但考虑到有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;  
}
  1. 由于有多组数据,每次输入前都要进行清空/初始化操作(养成好习惯);
  2. C++可直接使用bool类型,C语言需要include <stdbool.h>;
  3. 像这样输入复杂的题目,可以将样例输入包含到文件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 ;
}
  1. 经典的DFS结构,注意更新min_times时,也要更新TIMES数组(用于存储最优解);
  2. 注意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坐标是否冲突。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值