UVa10422 - Knights in FEN & luogu P2324 [SCOI2005] 骑士精神 题解
前言
其实是同一道题。在 vjudge 上搜 Knights in FEN
还有两题一模一样的。
由于数据范围不同,先讲 UVa10422,再讲骑士精神。
题目大意
给定一张 \(5\times 5\) 的棋盘,求给定状态移动到固定目标状态的最少步数。
多测。超过指定步数不予计算。
目标状态如下:
Knights in FEN
分析
在棋盘上跳马的问题,又发现目标状态固定且数据范围比较小,那么自然联想到搜索。这里多测数据组数 \(T\leq 13\),规定步数阈值是 \(10\) 步。
那么很简单,直接对其进行 bfs 即可。
代码
#include<bits/stdc++.h>
#define HohleFeuerwerke using namespace std
#pragma GCC optimize(3,"Ofast","-funroll-loops","-fdelete-null-pointer-checks")
#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#define int long long
HohleFeuerwerke;
inline int read(){
int s=0,f=1;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) s=s*10+c-'0';
return s*f;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x>=10) write(x/10);
putchar('0'+x%10);
}
const int MAXN=15;
const string dest="111110111100 110000100000";//目标状态
struct node{
string cur;
int stp;
};
int dx[8]={1,2,2,1,-1,-2,-2,-1};
int dy[8]={2,1,-1,-2,-2,-1,1,2};
inline int bfs(string str){
queue<node> q; q.push({str,0});
unordered_map<string,bool> mem;
while(!q.empty()){
node tmp=q.front(); q.pop();
if(tmp.cur==dest) return tmp.stp;
if(mem[tmp.cur]) continue;//记忆化
if(tmp.stp>=11) return 11;//阈值
mem[tmp.cur]=true;
//求空格位置
int blksp;
for(int i=0;i<tmp.cur.size();i++)
if(tmp.cur[i]==' ') blksp=i;
blksp++;
int x=(blksp-1)/5+1,y=(blksp-1)%5+1;
//枚举八个方向
for(int i=0;i<8;i++){
string nxt=tmp.cur;
int qx=dx[i]+x,qy=dy[i]+y;
if(qx>5||qx<=0||qy>5||qy<=0) continue;
swap(nxt[(qx-1)*5+qy-1],nxt[blksp-1]);//马跳过来后会在原位置留下空格,直接 swap 即可。
q.push({nxt,tmp.stp+1});
}
}
}
signed main()
{
int T=read();
while(T--){
string tmp,x="";
for(int i=1;i<=5;i++) getline(cin,tmp),x+=tmp;
int ans=bfs(x);
if(ans<=10) printf("Solvable in "),write(ans),puts(" move(s).");
else puts("Unsolvable in less than 11 move(s).");
}
}
//Unsolvable in less than 11 move(s).
//Solvable in 7 move(s).
骑士精神
分析
当步数阈值上升到 \(15\) 的时候很明显单向搜索的复杂度是飙升的,这时候可以使用双向搜索,分别从目标和起始状态开始搜索,当搜索到同一状态时结束。这种做法的优势是,只需要搜到阈值一半即可。
需要记录好每个状态是由哪里(目标/起点)转移过来的。
值得注意的是如果两边中如果有一边只动了一步,那么会多搜,这是因为 unordered_map
不能在第一轮更新。这个时候需要额外的判断是否直接到了终点即可。
代码
#include<bits/stdc++.h>
#define HohleFeuerwerke using namespace std
#pragma GCC optimize(3,"Ofast","-funroll-loops","-fdelete-null-pointer-checks")
#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#define int long long
HohleFeuerwerke;
inline int read(){
int s=0,f=1;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) s=s*10+c-'0';
return s*f;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x>=10) write(x/10);
putchar('0'+x%10);
}
const int MAXN=15;
const string dest="111110111100*110000100000";
struct node{
string cur;
int stp,type;
};
char a[MAXN][MAXN];
int dx[8]={1,2,2,1,-1,-2,-2,-1};
int dy[8]={2,1,-1,-2,-2,-1,1,2};
inline int bfs(string str){
queue<node> q; q.push({str,0,1});
q.push({dest,0,2});
unordered_map<string,int> mem1,mem2;
while(!q.empty()){
node tmp=q.front(); q.pop();
//从起点
if(tmp.type==1){
if(mem2[tmp.cur]) return mem2[tmp.cur]+tmp.stp;//如果终点来过,直接返回
if(tmp.cur==dest) return tmp.stp;
if(mem1[tmp.cur]) continue;
mem1[tmp.cur]=tmp.stp;
}
//从终点
if(tmp.type==2){
if(mem1[tmp.cur]) return mem1[tmp.cur]+tmp.stp;//如果起点来过,直接返回
if(tmp.cur==str) return tmp.stp;
if(mem2[tmp.cur]) continue;
mem2[tmp.cur]=tmp.stp;
}
if(tmp.stp>=9) return 16; // 注意这里要多搜一点,防止两边不均衡直接返回了
int blksp;
for(int i=0;i<tmp.cur.size();i++)
if(tmp.cur[i]=='*') blksp=i;
blksp++;
int x=(blksp-1)/5+1,y=(blksp-1)%5+1;
for(int i=0;i<8;i++){
string nxt=tmp.cur;
int qx=dx[i]+x,qy=dy[i]+y;
if(qx>5||qx<=0||qy>5||qy<=0) continue;
swap(nxt[(qx-1)*5+qy-1],nxt[blksp-1]);
q.push({nxt,tmp.stp+1,tmp.type});
}
}
}
signed main()
{
int T=read();
while(T--){
string x;
for(int i=1;i<=5;i++)
for(int j=1;j<=5;j++) cin>>a[i][j],x+=a[i][j];
int ans=bfs(x);
if(ans<=15) write(ans),puts("");
else puts("-1");
}
}
另外还有 A*,IDA*,IDDFS 等做法。