UVA10384 推门游戏 The Wall Pushers(IDA*,A*)

3 篇文章 0 订阅
2 篇文章 0 订阅

题目大意

给你一个 4 × 6 4\times 6 4×6 的网格图,网格边缘上可能有墙。对于每一个网格有一个权值 v a l val val,其中

v a l = 1 ( 如果这个网格左边缘(西边缘)有墙 ) + 2 ( 如果这个网格上边缘(北边缘)有墙 ) + 4 ( 如果这个网格右边缘(东边缘)有墙 ) + 8 ( 如果这个网格下边缘(南边缘)有墙 ) \begin{aligned}val= & & 1(\text{如果这个网格左边缘(西边缘)有墙})\\ & +&2(\text{如果这个网格上边缘(北边缘)有墙})\\&+&4(\text{如果这个网格右边缘(东边缘)有墙})\\&+&8(\text{如果这个网格下边缘(南边缘)有墙})\end{aligned} val=+++1(如果这个网格左边缘(西边缘)有墙)2(如果这个网格上边缘(北边缘)有墙)4(如果这个网格右边缘(东边缘)有墙)8(如果这个网格下边缘(南边缘)有墙)

然后你就可以通过这些网格的权值唯一确定这张网格图了。

然后题目还会给你一个起点坐标 ( s t x , s t y ) (st_x,st_y) (stx,sty)(注意,题目给的 s t x st_x stx s t y st_y sty 是反着输入的,即题目给出的顺序为: s t y st_y sty s t x st_x stx),然后你从这个起点开始走,每步你可以往上下左右四个方向移动一格,但有限制条件。

具体来讲,假设你当前所在格子为 ( x , y ) (x,y) (x,y),移动方向为 d i r dir dir ,下一步移动到的格子为 ( x x , y y ) (xx,yy) (xx,yy),那么:

  1. ( x , y ) (x,y) (x,y) ( x x , y y ) (xx,yy) (xx,yy) 之间没有墙时,你可以直接移动到 ( x x , y y ) (xx,yy) (xx,yy),整个过程算 1 1 1 步 。

  2. ( x , y ) (x,y) (x,y) ( x x , y y ) (xx,yy) (xx,yy) 之间有墙时,那么你可以把这堵墙往 d i r dir dir 方向推一格,但要保证推到的地方没有墙且在网格图内(含边界),然后再走到 ( x x , y y ) (xx,yy) (xx,yy),整个过程算 1 1 1 步,也就是说推墙操作不算入步数。

最后问你离开网格图的最少步数是多少。

样例解释:

一开始:

向上推墙:

向右推墙:

向下推墙:

沿着空格子走:

向右推墙:

向上推墙( 2 2 2 步):

直接走到终点:(路线也标出来了)

然后按东南西北大写缩写的方式输出路线:

N E S E S E E N N W N W W W W W NESESEENNWNWWWWW NESESEENNWNWWWWW

再不明白题目在讲什么我也无能为力了

题解

I D A ∗ IDA^* IDA 迭代加深搜索,关键是模拟推墙的过程需要细心观察题目的输入方法,发现把某一网格的权值转成二进制就变成了西北东南有没有墙的状态了。

所以用一些二进制运算操作就能模拟推墙过程了,比如删掉东边的一堵墙就是把权值减去 4 4 4,判断东边有没有墙就用权值并(&)上 4 4 4

注意推一堵墙会影响到很多格子。

至于估价函数,我们可以考虑算出当前点到每个出口的曼哈顿距离(横坐标之差加上纵坐标之差),再取最小值就好了。

完整代码加注释如下:

#include<bits/stdc++.h>

#define N 5
#define M 7
#define INF 0x7fffffff

using namespace std;

struct Point
{
	int x,y;
	Point(){};
	Point(int xx,int yy)
	{
		x=xx,y=yy;
	}
};

int stx,sty,a[N][M],ans[N*M];
int fx[]={0,-1,0,1},fy[]={-1,0,1,0},dir[]={1,2,4,8};
bool vis[N][M];

vector<Point>ed;//用来存所有出口的坐标

char change(int x)//把数字方向变成字符方向
{
	if(!x) return 'W';
	if(x==1) return 'N';
	if(x==2) return 'E';
	return 'S';
}

int check(int x,int y)//判断四周是否有出口
{
	if(y==1&&!(a[x][y]&1)) return 0;//西边是出口
	if(x==1&&!(a[x][y]&2)) return 1;//北边是出口
	if(y==6&&!(a[x][y]&4)) return 2;//东边是出口
	if(x==4&&!(a[x][y]&8)) return 3;//南边是出口
	return -1;//四周的格子没出口
}

int value(int x,int y)//估价函数
{
	int dis=INF;
	for(int i=0,size=ed.size();i<size;i++)
		dis=min(dis,abs(ed[i].x-x)+abs(ed[i].y-y));//曼哈顿距离
	return dis;
}

bool right(int x,int y)//判断是否在网格图内
{
	return x>=1&&x<=4&&y>=1&&y<=6;
}

bool IDAstar(int dep,int maxdep,int x,int y)//IDA*
{
	if(dep+value(x,y)+1>maxdep) return false;//剪枝
	int tmp=check(x,y);
	if(tmp!=-1)//有答案了
	{
		ans[dep+1]=tmp;
		return true;
	}
	for(int i=0;i<4;i++)
	{
		int xx=x+fx[i],yy=y+fy[i];
		if(!right(xx,yy)||vis[xx][yy])continue;//走过的不去
		if(!(a[x][y]&dir[i]))//(x,y)和(xx,yy)之间没有墙
		{
			vis[xx][yy]=true;
			ans[dep+1]=i;
			bool flag=IDAstar(dep+1,maxdep,xx,yy);
			vis[xx][yy]=false;
			if(flag) return true;//剪枝,一找到答案就回溯
		}
		else if(!(a[xx][yy]&dir[i]))//可以推墙
		{
        //下面以向东推墙举例,则墙原来在(x,y)东边、(xx,yy)西边
			a[x][y]-=dir[i];//因为把当前格东边的墙向东推了,所以东边的墙要减掉
			a[xx][yy]+=dir[i]-dir[(i+2)%4];//因为(xx,yy)西边的墙被推到了东边,所以要先减去西边,再加上东边
			int xi=xx+fx[i],yi=yy+fy[i];//(xx,yy)东边的格子(xi,yi)
			if(right(xi,yi))
				a[xi][yi]+=dir[(i+2)%4];//由于(xi,yi)西边原先没有墙,现在被推过来一堵墙,所以要加上西边的墙
			vis[xx][yy]=true;
			ans[dep+1]=i;
			bool flag=IDAstar(dep+1,maxdep,xx,yy);
			vis[xx][yy]=false;//还原
			if(right(xi,yi))
				a[xi][yi]-=dir[(i+2)%4];
			a[xx][yy]-=dir[i]-dir[(i+2)%4];
			a[x][y]+=dir[i];
			if(flag) return true;//剪枝
		}
	}
	return false;
}

int main()
{
	while(~scanf("%d%d",&sty,&stx)&&(stx||sty))
	{
		for(int i=1;i<=4;i++)
		{
			for(int j=1;j<=6;j++)
			{
				scanf("%d",&a[i][j]);
				if(j==1&&!(a[i][j]&1)) ed.push_back(Point(i,j));//看是否是出口
				else if(j==6&&!(a[i][j]&4)) ed.push_back(Point(i,j));
				else if(i==1&&!(a[i][j]&2)) ed.push_back(Point(i,j));
				else if(i==4&&!(a[i][j]&8)) ed.push_back(Point(i,j));
			}
		}
		vis[stx][sty]=true;
		for(int maxdep=1;;maxdep++)//不断加深最大步数
		{
			if(IDAstar(0,maxdep,stx,sty))
			{
				for(int i=1;i<=maxdep;i++)
					putchar(change(ans[i]));
				puts("");
				break;
			}
		}
		vis[stx][sty]=false;//不要忘了这步
	}
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值