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 等做法。

posted @ 2021-09-11 23:45  _HofFen  阅读(52)  评论(0编辑  收藏  举报