输入格式
第一行有一个正整数T(T<=10),表示一共有N组数据。接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑士,*表示空位。两组数据之间没有空行。
输出格式
对于每组数据都输出一行。如果能在15步以内(包括15步)到达目标状态,则输出步数,否则输出-1。
这题是一道比较好的\(A*\)的模板题,\(A*\)对dfs的优化一般叫\(IDA*\)。
首先,\(A*\)有一个定义式叫做:
\(f(n)=g(n)+h(n)\)
\(f(n)\)即为一点的估价函数,\(g(n)\)是这一点实际所用的步数(价值),\(h(n)\)是对未来的所需步数(代价)的完美预测(接近完美)。
通过这个定义式,只要有h(n),每次都可以算出一个点的估价,\(A*\)算法就是通过限制估价,使得在搜索的时候不要在无意义的道路上浪费时间。即,每次限制每个点的估价\(maxdep\),一旦估价超过,就停止搜索。
根据定义式,\(h(n)\)一定要\(<=\)实际的所需步数。
像这张图,如果\(h(A)\)估大了的话,\(f(A)\)也会相应变大,这样\(f(A)\)有可能会被maxdep给去掉,从而找不到正确答案。
所以,为了保证答案的正确性,\(h(A)\)一定要在\(<=\)实际的所需步数的前提下尽量的大,也就是尽量估得准。
那回到这题,题目要求在15步以内完成才输出步数,否则-1,所以可以将maxdep从0到15枚举,一旦成功找到,那就是答案(最少步数)。
而最优的就是每一次都将不在相应位置上的棋子移到相应的位置上,所以\(h(A)\)可以是不匹配的棋子数,因为每次最多还原一个,所以\(h(A)\)一定是\(<=\)实际的所需步数,正确性得到保证。
\(h(A)\):
int value()
{
int ans=0;
for(int i=0;i<5;i++)
{
for(int j=0;j<5;j++)
{
if(standard[i][j]!=tu[i][j])
{
ans++;
}
}
}
return ans;
}
然后就是时间的剪枝,题目要的是最少步数,所以肯定不能做回头路,还有就是没有找到完成的路径,满足这两点,加上maxdep的限制,才能继续往下搜。
其他就是一个暴力搜索了,没什么好讲的:
#include<bits/stdc++.h>
using namespace std;
int standard[5][5]={{1,1,1,1,1},{0,1,1,1,1},{0,0,-1,1,1},{0,0,0,0,1},{0,0,0,0,0}},tu[5][5],f[8][2]={{-2,-1},{-2,1},{1,2},{1,-2},{-1,2},{-1,-2},{2,-1},{2,1}},is,t,xx,yy;
char ch;
int value()//估价函数
{
int ans=0;
for(int i=0;i<5;i++)
{
for(int j=0;j<5;j++)
{
if(standard[i][j]!=tu[i][j])
{
ans++;
}
}
}
return ans;
}
void dfs(int dep,int x,int y,int maxdep,int lastway)//直接搜
{
if(dep==maxdep)
{
if(!value())
{
is=1;
}
return;
}
for(int i=0;i<8;i++)
{
int xx=x+f[i][0];
int yy=y+f[i][1];
if(xx<0||xx>4||yy<0||yy>4||7-i==lastway)//不走出格,不走回头路
{
continue;
}
swap(tu[x][y],tu[xx][yy]);
if(dep+value()<=maxdep&&!is)//估价函数的限制
{
dfs(dep+1,xx,yy,maxdep,i);
}
swap(tu[x][y],tu[xx][yy]);
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
is=0;
for(int i=0;i<5;i++)
{
for(int j=0;j<5;j++)
{
cin>>ch;
if(ch=='*')
{
tu[i][j]=-1;
xx=i;
yy=j;
}else{
tu[i][j]=ch-'0';
}
}
}
if(!value())
{
puts("0");
return 0;
}
for(int i=1;i<=15;i++)//A*
{
dfs(0,xx,yy,i,9);
if(is)
{
printf("%d\n",i);
break;
}
}
if(!is)//没有
{
puts("-1");
}
}
return 0;
}