P2324 [SCOI2005]骑士精神-搜索大练习

本文探讨了一种在5x5棋盘上移动骑士的算法,使用了宽度优先搜索(BFS)、双向BFS、A*算法及IDEA*等方法解决骑士从初始位置到达目标位置的最少步数问题。通过不同算法的对比,分析了每种算法的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

去年就想做这个题,一直拖到今年。照着lyyy的方法做。

5*5的棋盘,3种状态,3^25次方,longlong 可以存储。

一、用set判重状态。先写个宽搜试试,结果当然20分。

//修改自题解,笨笨的bfs,set判重
#include<iostream>
#include<cstdio>
#include<set>
#include<queue>
using namespace std;
typedef long long ll;
char c[6][6];
int dx[11]= {0,0,1,-1,1,-1,2,-2,2,-2},dy[11]= {0,0,2,-2,-2,2,1,-1,-1,1} ; //来回为相邻的两个方向
char mb[6][6]= {'0','0','0','0','0','0','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',};
ll goal;
struct node {
	char c[6][6];
	int x,y,step,cnt;//step已经走的步数,cnt还需要最少再走的步数,后面a*用的。 
	bool operator <(node a)const {
		return cnt+step>a.step+a.cnt;
	}
} head;
bool judge(int x,int y) {
	if(x<1||x>5)return false;
	if(y<1||y>5)return false;
	return true;
}
ll hs3(char d[][6]) {
	ll ans=0;
	for(int i=1; i<=5; i++)
		for(int j=1; j<=5; j++)
			ans=ans*3ll+d[i][j]-'0';
	return ans;
}
void swap(char &c1,char &c2) {
	char c3=c1;c1=c2;c2=c3;
}
queue<node> pq;
int bfs(node head) {
	if(head.cnt>15)return -1;	
	set<ll>st;
	while(!pq.empty())pq.pop();
	pq.push(head);
	st.insert(hs3(head.c));
	node p,tp;
	while(!pq.empty()) {
		p=pq.front();
		pq.pop();
		if(hs3(p.c)==goal)return p.step;
		for(int i=2; i<10; i++) {//扩展队首元素
			int tx=p.x+dx[i],ty=p.y+dy[i];
			if(!judge(tx,ty))continue;//越界不要,超过15不要
			tp=p;
			swap(tp.c[tx][ty],tp.c[p.x][p.y]);
			tp.step=p.step+1;
			if(tp.step>15)continue;
			tp.x=tx,tp.y=ty;
			ll tmp= hs3(tp.c);
			if(!st.count(tmp)){
				pq.push(tp);
				st.insert(tmp);
			}
			
		}
	}
	return -1;
}
int main() {
	int t,k;
	goal=hs3(mb);	//mb是目标状态
	scanf("%d",&t);
	while(t--) {
		k=0;
		int px,py;
		for(int i=1; i<=5; i++)
			for(int j=1; j<=5; j++)
				cin>>c[i][j];
		for(int i=1; i<=5; i++)//k是有多少骑士不在目的地
			for(int j=1; j<=5; j++) {
				if(c[i][j]=='*')px=i,py=j,c[i][j]='2';
				if(c[i][j]!=mb[i][j]&&c[i][j]!='2')k++;
				head.c[i][j]=c[i][j];
			}
		head.x=px,head.y=py;
		head.step=0,head.cnt=k;
		hs3(head.c);
		node tp=head;
		swap(tp.c[px][py],tp.c[px+1][py+2]);
		hs3(tp.c);
		cout<<bfs(head)<<endl;
	}
}

二、双向宽搜。因为这个题目多组数据,但目标状态是一样,因此先从目标状态扩展一次,用map记录下到达一半(大半)状态的步数。然后每次从给定状态扩展,从所有能扩展出的状态种取最小值。还可以用从头尾同时扩展的双向搜索,那么找到的第一个状态就是。

//笨笨的bfs,set判重,map记录第一次宽搜的步数。
#include<iostream>
#include<cstdio>
#include<set>
#include<queue>
#include<map>
#include<string.h>
using namespace std;
typedef long long ll;
char c[6][6];
int dx[11]= {0,0,1,-1,1,-1,2,-2,2,-2},dy[11]= {0,0,2,-2,-2,2,1,-1,-1,1} ; //来回为相邻的两个方向
char mb[6][6]= {'0','0','0','0','0','0','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',};
ll goal;
struct node {
	char c[6][6];
	int x,y,step,cnt;
	ll hs;
	bool operator <(node a)const {
		return cnt+step>a.step+a.cnt;
	}
} head;
bool judge(int x,int y) {
	if(x<1||x>5||y<1||y>5)return false;
	return true;
}
ll hs3(char d[][6]) {
	ll ans=0;
	for(int i=1; i<=5; i++)
		for(int j=1; j<=5; j++)
			ans=ans*+d[i][j]-'0';
	//	cout<<"hs3<<"<<ans<<endl;
	return ans;
}
void swap(char &c1,char &c2) {
	char c3=c1;c1=c2;c2=c3;
}
queue<node> pq;
map<ll,int>mp; 
void bfs0(node head) {
	while(!pq.empty())pq.pop();	
	pq.push(head);
	mp[head.hs]=head.step;
	node p,tp;
	while(!pq.empty()) {
		p=pq.front();
		pq.pop();
		if(p.step>8)break;		
		for(int i=2; i<10; i++) {
			int tx=p.x+dx[i],ty=p.y+dy[i];
			if(!judge(tx,ty))continue;//越界不要,超过15不要
			tp=p;
			swap(tp.c[tx][ty],tp.c[p.x][p.y]);
			tp.step=p.step+1;
			tp.hs=hs3(tp.c);
			tp.x=tx,tp.y=ty;
			if(!mp.count(tp.hs)){
				pq.push(tp);
				mp[tp.hs]=tp.step;
			}
			
		}
	}
	return;
}
int bfs(node head) {
	int ans=20;	
	set<ll>st;
	while(!pq.empty())pq.pop();	
	pq.push(head);
	st.insert(head.hs);
	node p,tp;
	while(!pq.empty()) {
		p=pq.front();
		pq.pop();
		if(mp.count(p.hs))ans=min(ans,p.step+mp[p.hs]);
		if(p.step>7)break;
		for(int i=2; i<10; i++) {
			int tx=p.x+dx[i],ty=p.y+dy[i];
			if(!judge(tx,ty))continue;//越界不要,超过15不要
			tp=p;
			swap(tp.c[tx][ty],tp.c[p.x][p.y]);
			tp.step=p.step+1;
			tp.x=tx,tp.y=ty;
			ll tmp= hs3(tp.c);
			if(!st.count(tmp)){
				pq.push(tp);
				st.insert(tmp);
			}
			
		}
	}
	if(ans>15)return -1;
	else return ans;
}

int main() {

	int t,k;
	memcpy(head.c,mb,36);
	head.hs=hs3(head.c);
	head.x=head.y=3;
	head.step=0;
	bfs0(head);
	scanf("%d",&t);
	while(t--) {
		k=0;
		int px,py;
		for(int i=1; i<=5; i++)
			for(int j=1; j<=5; j++)
				cin>>c[i][j];
		for(int i=1; i<=5; i++)//k是有多少骑士不在目的地
			for(int j=1; j<=5; j++) {
				if(c[i][j]=='*')px=i,py=j,c[i][j]='2';
				if(c[i][j]!=mb[i][j]&&c[i][j]!='2')k++;
				head.c[i][j]=c[i][j];
			}
		head.x=px,head.y=py;
		head.step=0,head.cnt=k;
		head.hs=hs3(head.c);
		cout<<bfs(head)<<endl;
	}
}

三、Astar算法

用这个题目好像理解了(1)“估价函数要比最优结果还要优”的原则。和宽搜的不同之处在于,当前状态扩展出去的状态都入优先队列,(2)从优先队列出来时候,这个状态才做标记,因为处理过的才是估价后最优的,随着确定状态的不断增多,估价函数只会将最接近目标状态的值放在前面,这样(3)第一个找到的状态就是目标状态。

//修改自题解
#include<iostream>
#include<cstdio>
#include<set>
#include<queue>
using namespace std;
typedef long long ll;
char c[6][6];
int dx[11]= {0,0,1,-1,1,-1,2,-2,2,-2},dy[11]= {0,0,2,-2,-2,2,1,-1,-1,1} ; //来回为相邻的两个方向
char mb[6][6]= {'0','0','0','0','0','0','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',};
ll goal;
struct node {
	char c[6][6];
	int x,y,step,cnt;
	ll hs;
	bool operator <(node a)const {
		return (cnt+step)>(a.step+a.cnt);
	}
} head;
bool judge(int x,int y) {
	if(x<1||x>5||y<1||y>5)return false;
	return true;
}
ll hs3(char d[][6]) {
	ll ans=0;
	for(int i=1; i<=5; i++)
		for(int j=1; j<=5; j++)
			ans=ans*3ll+d[i][j]-'0';
	return ans;
}
int  get_cnt(char d[][6]) {
	int ans=0;
	for(int i=1; i<=5; i++)
		for(int j=1; j<=5; j++)
			if(d[i][j]!='2'&&d[i][j]!=mb[i][j])ans++;
	return ans;
}
void print(char c[][6]) { //调试用,可无视
	for(int i=1; i<=5; i++) {
		for(int j=1; j<=5; j++)cout<<c[i][j];
		cout<<endl;
	}
	cout<<endl;
}
void swap(char &c1,char &c2) {
	char c3=c1;
	c1=c2;
	c2=c3;
}
priority_queue<node> pq;
set<ll>st;
int Abfs(node head) {
	if(head.cnt>15)return -1;
	st.clear();
	while(!pq.empty())pq.pop();
	pq.push(head);
	node p,tp;
	while(!pq.empty()) {
		p=pq.top();
		while(!pq.empty()&&st.count(p.hs)){
			pq.pop();
			if(!pq.empty())p=pq.top();
		}
    	if(p.hs==goal)return p.step;
		if(!pq.empty())pq.pop();
		st.insert(p.hs);
		for(int i=2; i<10; i++) {
			tp=p;
			tp.x=p.x+dx[i],tp.y=p.y+dy[i];
			if(!judge(tp.x,tp.y))continue;//越界不要,超过15不要
			tp.cnt=p.cnt-(p.c[tp.x][tp.y]!=mb[tp.x][tp.y]);
			tp.cnt+=(p.c[tp.x][tp.y]!=mb[p.x][p.y]);
			swap(tp.c[tp.x][tp.y],tp.c[p.x][p.y]);
			tp.step=p.step+1;
			if(tp.step+tp.cnt>15)continue;
			tp.hs=hs3(tp.c);
			pq.push(tp);
		}
	}
	return -1;
}

int main() {
	int t,k;
	goal=hs3(mb);
	scanf("%d",&t);
	while(t--) {
		k=0;
		int px,py;
		for(int i=1; i<=5; i++)
			for(int j=1; j<=5; j++)
				cin>>c[i][j];
		for(int i=1; i<=5; i++)//k是有多少骑士不在目的地
			for(int j=1; j<=5; j++) {
				if(c[i][j]=='*')px=i,py=j,c[i][j]='2';
				if(c[i][j]!=mb[i][j]&&c[i][j]!='2')k++;
				head.c[i][j]=c[i][j];
			}
		head.x=px,head.y=py;
		head.step=0,head.cnt=k;
		head.hs=hs3(head.c);
		cout<<Abfs(head)<<endl;
	}
}

四、IDEA*

IDE就是按层展开的dfs,增加一个搜索层数的限制条件,在dfs内用A*就是扩展节点是加了带估价函数的剪枝,如果当前状态用的步数+还有走的最优步数也无法到达目标,那就不要走下去了。代码量比较小。

//修改自题解 
#include<iostream>
#include<cstdio>
using namespace std;
char c[6][6];
int dx[11]= {0,0,1,-1,1,-1,2,-2,2,-2},dy[11]= {0,0,2,-2,-2,2,1,-1,-1,1} ; //来回为相邻的两个方向
string mb[6];
bool judge(int x,int y) {
	if(x<1||x>5)return false;
	if(y<1||y>5)return false;
	return true;
}
void print() { //调试用,可无视
	for(int i=1; i<=5; i++) {
		for(int j=1; j<=5; j++)cout<<c[i][j];
		cout<<endl;
	}
	cout<<endl;
}
void swap(char &c1,char &c2) {
	char c3=c1;
	c1=c2;
	c2=c3;
}
int i,j,k,m,n,ans;
void trys(int step,int cnt,int px,int py,int last) { //x是当前搜索层数,y是剩余多少骑士未到达,pxpy是*的位置,last是上层搜索操作
	if(step+cnt>j)return;//剪枝
	if(ans>0)return;//出解后不必继续搜索
	if(cnt==0) {
		ans=step;//print();
		return;
	}
	for(int i=2; i<10; i++) {
		if(judge(px+dx[i],py+dy[i])&&(last^i)!=1) {
			int now=cnt+(c[px+dx[i]][py+dy[i]]==mb[px+dx[i]][py+dy[i]]);//找骑士 
			now-=(c[px+dx[i]][py+dy[i]]==mb[px][py]);//感筛省?
			swap(c[px+dx[i]][py+dy[i]],c[px][py]);
			trys(step+1,now,px+dx[i],py+dy[i],i);
			swap(c[px+dx[i]][py+dy[i]],c[px][py]);
		}
	}
}
int main() {
	mb[1]="011111";
	mb[2]="001111";
	mb[3]="000*11";
	mb[4]="000001";
	mb[5]="000000";
	//mb是目标状态
	int t;
	scanf("%d",&t);
	while(t--) {
		k=0;
		for(i=1; i<=5; i++)
			for(j=1; j<=5; j++)
				cin>>c[i][j];
		for(i=1; i<=5; i++)
			for(j=1; j<=5; j++)
				if(c[i][j]!=mb[i][j]&&c[i][j]!='*')k++;//k是有多少骑士不在目的地
		int px,py;
		for(i=1; i<=5; i++)
			for(j=1; j<=5; j++)
				if(c[i][j]=='*')px=i,py=j;
		ans=0;
		for(j=k; j<=15&&ans==0; j++) //迭代加深
			trys(0,k,px,py,0);
		if(ans>0)
			printf("%d\n",ans);
		else
			printf("%d\n",-1);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值