码迷,mamicode.com
首页 > 其他好文 > 详细

【日常学习】【迭代加深搜索+哈希】codevs1004 四子连棋题解

时间:2015-08-25 16:45:26      阅读:935      评论:0      收藏:0      [点我收藏+]

标签:迭代加深搜索   hash   搜索   日常学习   

转载请注明出处 [ametake版权所有]http://blog.csdn.net/ametake

题目描述 Description

在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白色棋子,7颗黑色棋子,有两个空白地带,任何一颗黑白棋子都可以向上下左右四个方向移动到相邻的空格,这叫行棋一步,黑白双方交替走棋,任意一方可以先走,如果某个时刻使得任意一种颜色的棋子形成四个一线(包括斜线),这样的状态为目标棋局。

 
 

 

输入描述 Input Description
从文件中读入一个4*4的初始棋局,黑棋子用B表示,白棋子用W表示,空格地带用O表示。
输出描述 Output Description

用最少的步数移动到目标棋局的步数。

样例输入 Sample Input

BWBO
WBWB
BWBW
WBWO

样例输出 Sample Output

5

经历了六个小时的努力,这道题目终于AC了···看题解上的反应,刘哥小时不算个例,大有人在啊,连黄学长也花了一个多小时。不过,要是NOIP赛场上碰到这道题,我还不得调试到歇菜?所以说,还是要好好增强代码能力,避免细节上的失误。

是的,这道题目看似简单,但是细节非常多,需要仔细注意。

先说一下思路吧!


一看当然是直接搜,那么采用深搜还是广搜呢?通常情况下,我们考虑广搜,因为这道题要求球最少步数,且很难剪枝,深搜不起效果。那么为什么我们采用了迭代加深搜索呢?首先要考虑的是,广搜有可能爆空间(这道题目并不会爆空间,但是通常使用迭代加深都是因为广搜可能超出空间,而迭代加深本质上就是一种时间换空间的策略,它的复杂度比深搜要高很多,按完全二叉树算要高一倍,但是空间消耗少)。而我们在这里考虑的主要是:黑白棋子都可能先走,如果广搜我个人可能实现上有困难,因为要交替入队。这道题我成功地写出ID,某种意义上是撞大运了而已,ID比广搜快很多大概也和数据有关,正解还应当是广搜。


我们每个阶段(就是步数限制)搜索两次,第一次黑棋先行,第二次白棋先行。每次搜索又循环两次,因为有两个空格。


这道题的启发之一是:不必将数组作为参数传递到函数中,只需作为全局变量,每次修改后回溯即可。我传递了两个参数,一个是当前层数,其实如果把我的好这个也不必传递,但传递了会好写一些;另一个是这一步该走黑棋还是白棋,不难看出,这个也可以不传递。也就是说什么参数都不传递也可以。


如何判断是否达到目标状态?参考黄学长的做法:

bool equ(int &a1,int &a2,int &a3,int &a4)
{
    if (a1!=a2||a2!=a3||a3!=a4||a4!=a1) return 0;
	return 1;
}

inline bool check()
{
     for(int i=0;i<4;i++)
        {
             if(equ(e.a[i][0],e.a[i][1],e.a[i][2],e.a[i][3]))return 1;
             if(equ(e.a[1][i],e.a[2][i],e.a[3][i],e.a[0][i]))return 1;
         }
     if(equ(e.a[1][1],e.a[2][2],e.a[3][3],e.a[0][0]))return 1;
     if(equ(e.a[0][3],e.a[1][2],e.a[2][1],e.a[3][0]))return 1;
     return 0;
}
这里,黄学长存图的坐标是1-4,而我是0-3,可真是坑死我了= =一开始没注意,忘了修改,以至于单步了很多遍才发现···

那么,如何判断状态是否重复呢?之前我们用了set,然而据里奥神犇介绍,不开O2优化的set要比哈希表慢一半(当然了,哈希表只是数组和取模而已)。因此我们尝试了哈希表。这篇文章之所以是“日常学习”,正是因为这是哈希表的第一次出现。

哈希的代码很简单:

</pre>首先开一个4000000的bool数组hash<pre name="code" class="cpp">inline bool gethash(const node &e,const int &depth)
{
	int va=0;
	for (int i=0;i<4;i++)
	{
		for (int j=0;j<4;j++)
		{
			va=va*3+e.a[i][j];
		}
	}
	va%=3733799;
	if (hash[va])) return false;
	hash[va]=true;
	return true;
}

上面是我最初写的哈希表。这个代码拿到广搜里可以直接用,但是迭代加深不行!为什么呢?

考虑这样一种情况:某个方向上,深度限制为5,我们在第四步找到了这个状态,打上哈希,之后发现这个方向上并无正解。我们又向另一个方向走,发现第二步又走到了这个状态,然而它已经被打上哈希了,因此我们跳过。但实际上,从这个状态走三步后正是目标状态,应当输出5。这可怎么办呢?

我们仔细观察,为什么广搜不会出现这种情况?因为广搜是严格按层扩展,如果这个状态被找到一定是在最近的位置(第二步)被找到。因此我们可以设一个数组have,存储被哈希过的数是在第几步被找到的。再次遇到该状态时,判断一下是否当前走的步数比have数组中存的步数少,如果少,我们把它继续向下搜索。

修改后的代码是这样的:

inline bool gethash(const node &e,const int &depth)
{
	int va=0;
	for (int i=0;i<4;i++)
	{
		for (int j=0;j<4;j++)
		{
			va=va*3+e.a[i][j];
		}
	}
	va%=3733799;
	if (hash[va]&&(have[va]<=depth)) return false;
	hash[va]=true;
	have[va]=depth;
	return true;
}

这里还应注意的一个问题是,迭代加深搜索每次加深都应该将哈希表清空!!!

这是因为,迭代加深每次都是从第一层开始搜,如果不清空,一开始的状态在上一次搜索时已经被哈希,就无处可搜了。

那么代码君奉上:


今天中午知道了很多。originlab君,你的帮助真的让我非常感激,素日一直默默奉献,而且我今天凌晨给你发的私信竟然那么快就收到回信,介绍的很详细,实在感动。今天才知道电骡,以及电骡用户们,一群坚持着的人们。我发现互联网的神秘面纱正在慢慢揭开,让我看到一个全新的世界。所以还是要好好学,努力学才能做一个合格的技术宅(ˉ﹃ˉ)


——遗民泪尽胡尘里,南望王师又一年


版权声明:转载请注明出处 [ametake版权所有]http://blog.csdn.net/ametake欢迎来看

【日常学习】【迭代加深搜索+哈希】codevs1004 四子连棋题解

标签:迭代加深搜索   hash   搜索   日常学习   

原文地址:http://blog.csdn.net/ametake/article/details/47976669

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!