ZOJ - 1505 Solitaire 【双向BFS】(好题)

题目链接

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1505

题意 一个8 * 8 的棋盘上面有四个棋子

棋子可以上下左右移动,如果隔壁有个棋子 那就可以跳一步,只能跳一步。

给出 初始状态,和末尾状态 求能不能在8步之内达到

思路乍一眼看,这题就是一个搜索,但是虽然只有8步,但是每步都有4个点可以选择,每个点又有4个方向,所以复杂度为:(4*4)^8,可谓爆炸,那么就容易想到双向bfs,可以减小到2*(4*4)^4,起点和终点同时bfs,若在步数之内相交,说明是可行的。这里又有个难点:如何判断相交?首先对于这个8*8的图进行压缩,成一个100000000的数来表示状态,然后因为这个数太大,不能用数组来表示,所以我们用map来保存这个状态值,map的dis,来表示该搜索方式是否已经被搜索到,并且保存搜索到的步数长度

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <map>
#include <vector>
using namespace std;
#define N 8 // 图的大小

int g[N][N];
int godir[][2] = {{-1,0}, {0,1}, {1,0}, {0,-1}};

int gtoi (int a[][N]) {
	int rs = 0;
	int loc = 10000000;
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < N; ++j) {
			if (a[i][j]==1) {
				rs += i*loc;
				loc /= 10;
				rs += j*loc;
				loc /= 10;
			}
			if(loc==1) 	break;
		}
		if(loc==1) 	break;
	}
	return rs;
}

void itog(int a[][N], int st) {
	for(int i = 0; i < N; ++i) memset(a[i], 0, sizeof(a[i]));
	int loc = 10000000;
	int x,y;
	for (int i = 0; i < 4; ++i) {
		x = st/loc%10;
		loc /= 10;
		y = st/loc%10;
		loc /= 10;
		a[x][y] = 1;
	}
}

void show(int a[][N]) {
	for(int i = 0; i < N; ++i) {
		for(int j = 0; j < N; ++j) printf("%d",a[i][j]);
		puts("");
	}
	puts("");
}
void show(int sta) {
	int a[N][N];
	itog(a, sta);
	show(a);
}

void findadj (vector<int> &adj, const int fr) {
	adj.clear();
	int tmp[N][N];
	int ball[4][2];
	int loc = 10000000;
	for (int i = 0; i < 4; ++i) {
		ball[i][0] = fr/loc%10;
		loc /= 10;
		ball[i][1] = fr/loc%10;
		loc /= 10;
	}
	for(int i = 0; i < 4; ++i) {
		for(int j = 0; j < 4; ++j) {
			itog(tmp, fr);
			int x = ball[i][0] + godir[j][0];
			int y = ball[i][1] + godir[j][1];
			int flag = 1;
			if( 0 <= x && x < N && 0 <= y && y < N && tmp[x][y]!=1) {
				flag = 0;
				tmp[x][y] = 1;
				tmp[ball[i][0]][ball[i][1]] = 0;
				adj.push_back( gtoi(tmp) );
			}
			if(flag) {
				x = ball[i][0] + godir[j][0]*2;
				y = ball[i][1] + godir[j][1]*2;
				if( 0 <= x && x < N && 0 <= y && y < N && tmp[x][y]!=1) {
					tmp[x][y] = 1;
					tmp[ball[i][0]][ball[i][1]] = 0;
					adj.push_back( gtoi(tmp) );
				}
			}
		}
	}
}

int dsbfs(int src, int des) {//传入起点状态,终点状态
	int dir = 0; // 表示方向 
	int e[2]; // 目标状态 
	queue<int> que[2]; // 队列(两个队列,0保存起始状态的搜索,1表示结束状态的搜索) 
	/*dis 用来记录距离,由于状态只有8^8=16777216 个,但是为了方便,
	  用到了8位十进制的整数来保存,这样就有10^8=100000000 个数了,内存肯定溢出
	  所以用map来保存距离*/
	map<int, int> dis[2];
	vector<int> adj; // 寻找fr扩展的节点 
	e[0] = des,	e[1] = src; // e表示dir应该搜索的目标
	//e[0]的目标为终点des,e[1]的目标表示为起点src
	//搜索0的搜索起点为src,搜索1的起点为des
	que[0].push(src), que[1].push(des);
	//初始化起点向终点搜索的起点为src步长为1,终点向起点的搜索起点为des步长为1
	dis[0][src] = 1, dis[1][des] = 1;
	/* 开始双向搜索啦,双向广搜 */
	while(!que[0].empty() || !que[1].empty()) {
		dir = que[0].size() <= que[1].size() ? 0 : 1; // 找节点少的队列进行扩展 ,减少复杂度
		if(que[dir].empty() && !que[!dir].empty()) dir = !dir;  // 如果一个队列为空了就找剩下一个队列
		int fr = que[dir].front();//取出可以取出的头节点
		que[dir].pop();//并删除
		int frdis = dis[dir][fr];
		if(e[dir]==fr) { // 找到了对方 
			return frdis;
		}
		if(dis[!dir][fr]) { // 搜索到对方扩展的状态就可以返回了 
			return dis[!dir][fr] + frdis - 1;
		}
		findadj(adj, fr);
		for(int i = 0; i < adj.size(); ++i) {
			if(dis[dir][adj[i]]==0 && frdis + 1 <= 5) { // 己方搜索到的状态不会重复并且距离最短 
				dis[dir][adj[i]] = frdis + 1;
				que[dir].push(adj[i]);
			}
		}
	}
	return 0;
}

int main () {
	int x, y;
	int src, des;//起点,终点状态
	while(scanf("%d", &x) - EOF) {
		// 第一个图的输入
		scanf("%d", &y);
		memset(g, 0, sizeof(g));
		g[x-1][y-1] = 1;
		for (int i = 0; i < 3; ++i) {
			scanf("%d%d", &x, &y);
			g[x-1][y-1] = 1;
		}
		src = gtoi(g);//通过已经得到的图状态来获得起点状态值
		memset(g, 0, sizeof(g));
		// 第二个图的输入
		for (int i = 0; i < 4; ++i) {
			scanf("%d%d", &x, &y);
			g[x-1][y-1] = 1;
		}
		des = gtoi(g);//得到得到第二个图的状态来得到终点状态值
		printf("%s\n", dsbfs(src, des)? "YES" : "NO");//如果8步之内发现两者相交,说明ok
	}
	return 0;
}




猜你喜欢

转载自blog.csdn.net/running_acmer/article/details/80573033