[SCOI2005]骑士精神
题目描述
在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位。在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2,纵坐标相差为1的格子)移动到空位上。 给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘: 为了体现出骑士精神,他们必须以最少的步数完成任务。
输入格式
第一行有一个正整数T(T<=10),表示一共有N组数据。接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑士,*表示空位。两组数据之间没有空行。
输出格式
对于每组数据都输出一行。如果能在15步以内(包括15步)到达目标状态,则输出步数,否则输出-1。
不难想到搜索可以做这题。由于骑士只能往空格走,与其枚举每个骑士可以走的方向不如直接搜索空格往哪走。我们尝试用深搜做这题。
很容易想出状态:当前的棋盘,空格的位置,走的步数。棋盘可以开个全局变量来存。每次空格可以往8个方向走,只需往8个方向分别拓展即可。
考虑剪枝:
1.上下界剪枝:
上界:题目给的是15次。
下界:最好的情况莫过于跳
\[\sum_{i=1}^{n}\sum_{j=1}^{n}[g[i][j]≠des[i][j]]
\]
次了。虽然这对剪枝没什么用
2.搜索顺序:由于每个格子可以重复走,所以顺序随意。
3.排除等效冗余:往前走了之后又往回走是没有意义的,所以我们应该避免走上一次走的方向的反方向。
4.最优化剪枝:如果当前搜到的答案为ans,那么当步数≥ans时就没必要往下搜了,回溯。
5.记忆化:没什么好记的,而且记了容易MLE。跳过。
有了这么多剪枝,你的搜索已经很快了,但仍然不尽人意。由于本题的答案很小,我们考虑进一步优化。
把DFS加上迭代加深。从1开始枚举搜索深度,当搜索到答案时必定是最优解,所以最优化剪枝就没必要做了。如果搜到了上界15,那么就是无解。于是我们的上下界剪枝也没有必要做了。
然而IDFS仍然不够快,因为这题的答案也并不像某些ans≤5那么小,当我们的深度枚举到ans时,我们面对的搜索树其实是非常大的,然而剪枝已经减不掉了。
根据IDFS的性质,像广搜一样,当第一次搜到答案时就是最优解,直接回溯。那我们管它多大的搜索树,我们只让程序快点搜出答案,即优先往答案所在的方向走即可。这就要用到启发式搜索IDA * 了。
设计估价函数。由于估价函数必须小于等于实际值,我们可以结合之前的下界来设计。也就是说,估价函数就是当前状态与目标状态不同的格子数:
\[f(g[][])=\sum_{i=1}^{5}\sum_{j=1}^{5}[g[i][j]≠des[i][j]]
\]
当当前深度加上预估值大于了搜索深度时就可以不往下搜了。
#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 6
using namespace std;
const int dir[8][2]={{1,2},{2,1},{2,-1},{1,-2},{-1,2},{-2,1},{-2,-1},{-1,-2}};
const int des[maxn][maxn]={{},{0,1,1,1,1,1},{0,0,1,1,1,1},{0,0,0,2,1,1},{0,0,0,0,0,1},{0,0,0,0,0,0}};
int g[maxn][maxn],ans;
inline int evaluate(){
int cnt=0;
for(register int i=1;i<=5;i++) for(register int j=1;j<=5;j++) if(g[i][j]!=des[i][j]) cnt++;
return cnt;
}
inline bool check(const int &x,const int &y){ return x<1||5<x||y<1||5<y; }
void IDAstar(int x,int y,int dep,int pre_dir,const int &lim){
if(dep==lim){ if(!evaluate()) ans=true; return; }
for(register int i=0;i<8;i++) if(i+pre_dir!=7){
int tx=x+dir[i][0],ty=y+dir[i][1];
if(!check(tx,ty)){
swap(g[x][y],g[tx][ty]);
if(evaluate()+dep<=lim) IDAstar(tx,ty,dep+1,i,lim);
swap(g[x][y],g[tx][ty]);
if(ans) return;
}
}
}
int main(){
int t,sx,sy; scanf("%d",&t);
while(t--){
for(register int i=1;i<=5;i++){
for(register int j=1;j<=5;j++){
char in; cin>>in;
if(in=='*') g[i][j]=2,sx=i,sy=j;
else g[i][j]=in-'0';
}
}
if(!evaluate()){ puts("0"); continue; }
ans=false;
for(register int maxdep=1;maxdep<=15;maxdep++){
IDAstar(sx,sy,0,-1,maxdep);
if(ans){ printf("%d\n",maxdep); goto Lzs; }
}
puts("-1");
Lzs:;
}
return 0;
}