四川省选:2005SCOI 骑士精神【例题详解】

咳咳咳,第一次,找了一道有意思的搜索题目,算法嘛,就是普通的深搜(dfs),迭代加深(id);

对于算法初学者,搜索是必备的技能了,搜索中最常见的问题,不过一个表格,或者是一个地图,或者是个迷宫,问能否走到终点或者是最少需要多少步(看上去和本题比较像)。但有的时候会有比较不一样的题目,比如三维啊,马走日(跟这个题有微妙的关系)象走田啊,之类之类的,便不在此赘述了;

考虑到有和我一样的萌新宝宝,在下详细谈谈对于迭代加深(Iterative Deepening)的思考和理解;首先是引用一段死知识点,大家尽量看懂:

    http://blog.csdn.net/alan_lon_/article/details/79392948

这是我为这道题引来的知识点;大家应该能(应该能!应该能!)看懂,不过要是真没看懂的话,也没关系,我可是很耐心的;

一言以蔽之,迭代加深就是在我们能预估搜索层数较少的时候,避免逐枝搜索造成深层搜索对时间的浪费。方法就是逐层扩展,一层一层从浅到深作深搜,直到搜到目标节点!

好进入正题,我从洛谷把这道题扒下来:

输入输出格式

输入格式:

第一行有一个正整数T(T<=10),表示一共有N组数据。接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑士,*表示空位。两组数据之间没有空行。

输出格式:

对于每组数据都输出一行。如果能在15步以内(包括15步)到达目标状态,则输出步数,否则输出-1。

样例输入

2
10110
01*11
10111
01001
00000
01011
110*1
01110
01010
00100

样例输出

7
-1

解析:

这道题就是说,有一个残局棋盘,二十四个骑士(骑士就是中国象棋的马),黑白各一半,要求最后在十五步之内尽量快的把二十四个骑士弄成左下右上各十二,超过了十五步就输出-1;

这时我一拍桌子!喝!这不就是迭代加深么,多么明显的“暗示”呵。(对不起~在下失态了)

于是我三下五除二写下了深搜,迭代加深,但是我意识到一个很严重的问题,迭代加深了半天,该错还是错,我天,这时什么操作,我明明已经击中他的要害了啊;

然后我参照了题解,才明白的(本人智商极低,那题解太简单我看不懂,看了半天才稍微明白了点,所以我立志要写天下最好懂的题解),原来,这一步搜下来,按照遍历的态度肯定就又搜回去了,搜回去,搜过来,搜回去,搜过来,这不就完了么;

所以,遍历八个方向的顺序就尤为重要了,避免搜回去加一组特判,完美解决啊;

嗯省选题就是省选题,不简单的,果不其然没法写;为甚么呢,因为想的太简单了,搜索之外的主函数太过暴力。(其实搜索写的也蛮乱的,就算主函数写对了也过不了)于是又打开详细题解看,呃呃呃,我明白了,原来搜索的分类讨论很乱,主函数调用搜索函数也不得当;

因为残局和目标棋局肯定有所不同,有多少不同?能否量化?不能确切说,但是肯定有个下限。到底有多少个位置残局和目标局是不同的,那就说明最少走多少步可能把残局复原;

于是乎,我们就从这个数开始搜,搜到十六步(超过15的第一个整数);那么这道题就可以完美解决了;

好,上面说的都是大概思路,咱现在唠一唠搜索的细节,与其他搜索不同的是,这个走棋的搜索,可以分为四种情况,好,现在为大家引用几句大神的题解;

1,原来骑士归位了,改完没归位(当然需要把差异度加一)

2,原来骑士没归位,改完归位了(当然就要把差异度减一)

3,原来空位没归位,改完归位了(同1)

4,原来空位归位了,改完没归位(同2)

没听懂吗?没关系,下面附的题解代码,有我逐句的详细解释,一定让你看懂(再看不懂。。。那你。。。再看一遍);

//【生,须为自己选条路,无论走不走得通】----By:霁月; 
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
using namespace std;
int b[6][6]={{},
	{0,1,1,1,1,1},
	{0,0,1,1,1,1},
	{0,0,0,-1,1,1},
	{0,0,0,0,0,1},
	{0,0,0,0,0,0}
};// 这是要记录我最终要把棋盘走成什么模样; 
int a[6][6];//这个存放我输入进来的残局棋盘; 
int xx[8]={-2,-2,-1,1,-1,1,2,2};
int yy[8]={-1,1,2,2,-2,-2,-1,1};//搜索套路,把所有的可能步子记下来; 
int md;//呃···这个变量名没有恶意,是搜索的最大深度,用来限制搜索深度; 
int search(int k,int x,int y,int tot,int last)//变量名十分标准,代表搜了k层了,从tot层(差异度为tot)开始搜的,当前点是x,y(其实开始就是那个空位)上一个搜的last节点; 
{
	if(k+tot>md)return 0;//太深了没搜到,直接搜索失败 
	if(tot==0)return 1;//与目标棋局的差异度为零,完全恢复,搜到了; 
	int nx,ny,p;//代表下一个节点(nx,ny)和p代表与目标棋盘的差异度tot(至少搜索层数)的复件,便于修改 
	bool fl=0; 
	for(int i=0;i<8;i++)//搜索遍历八种走法 
	{
		if(i!=7-last){/*重点来了,这样可以避免返回上一步搜索,造成死循环,不信照着上面xx,yy看一看*/ 
			nx=x+xx[i];ny=y+yy[i];p=tot;//遍历,存tot值以备下文选用 
			if(nx<=5&&nx>=1&&ny<=5&&ny>=1){/*判断没有走出格*/ 
				if(a[nx][ny]==b[nx][ny]&&a[nx][ny]!=b[x][y]) p++;
				if(a[nx][ny]!=b[nx][ny]&&a[nx][ny]==b[x][y]) p--;
				if(b[nx][ny]==-1) p--;
				if(b[x][y]==-1) p++;//以上这四种情况对于差异度的修改,可以对照我题解中写的四种情况来看; 
				a[nx][ny]^=a[x][y],a[x][y]^=a[nx][ny],a[nx][ny]^=a[x][y];
		//这是在走棋,用的是异或的手段,不信可以手动模拟;一会我也会为大家模拟; 
				fl=search(k+1,nx,ny,p,i);
				if(fl)return 1;//如果按照这个最大深度往下搜能搜到,那就直接能搜到了; 
				a[nx][ny]^=a[x][y],a[x][y]^=a[nx][ny],a[nx][ny]^=a[x][y];//不幸的是我没搜到,那就再把原棋盘恢复下一轮继续搜,免得搜乱了(回溯) 
			}
		}
	}
	return 0;//怎么都没搜到,那就只好return 0; 
}
int main()
{
	int t;char c;
	scanf("%d",&t);
	while(t--)
	{
		int x,y;
		int nm=0;
		for(int i=1;i<=5;i++){
			for(int j=1;j<=5;j++){
				cin>>c;
				if(c=='*') {
					a[i][j]=-1;
					x=i;y=j;
				}
				else{
					a[i][j]=c-'0';
				}//以上是输入,做过题的宝宝应该都会; 
				if(b[i][j]!=a[i][j]){
					nm++;
//为了把当前残局和目标棋局对比,最幸运的情况需要多少步,即记录下不一样的棋子数量,那么从这个数量开始搜; 
				}
			}
		}
		bool flag=0;//标记我是否找到了输出的步数,不理解的看下文代码 
		for(int i=nm;i<=16;i++)//既然十五步以上不算数,那么就搜到十五步以上吧; 
		{
			md=i;//这是我的不同深度 
			if(search(0,x,y,nm,-1))//找到了那么输出,然后告诉flag,不用输出-1了,我找到了; 
			{
				cout<<i-1<<endl;
				flag=1;
				break;
			}
		}
		if(flag==0)//没有人告诉我找到了解法,那就是没找到,输出-1; 
		{
			cout<<-1<<endl;
		}
	}
	return 0;//完美AC; 
}

 

posted @ 2018-04-30 20:27  杜宇一声  阅读(190)  评论(0编辑  收藏  举报