NOIP2013 DAY2 T3 华容道(PUZZLE) BFS+SPFA

题目点这里


题目大意:

有n*m(n<=30 m <= 30)个格子,有可移动的棋子固定的棋子,分别用1,0表示,玩q(q<=500)次,每次给出空白格子的位置(EX,EY),指定棋子的位置(SX,SY),终点的位置(TX,TY),问最少多少步使指定棋子到终点棋子,如果不能达到就输出-1;



解题思路:

若指定的棋子(S)到终点(T)去,必定会先让空白格(E)到达S的上下左右的某个位置,然后通过这个空白块经过一系列复杂漫长的操作到达终点,首先处理出E到S旁边的花费W,然后以1的时间到达,总花费为W+1,设立一个数组dp,dp[x][y][k]表示一个格子走到(x,y)的k方向上的花费,W就解决了;以状态[x][y][k]作为一个顶点看成图,求最短路用spfa,怎么搞?设立一个数组step[x][y][r][k]表示空白格在(x,y)的r方向上,然后(x,y)走到r方向的相邻位置,即(x+dix[r],y+diy[r])到(x+dix[k],y+diy[k])的花费,这里就可以这样较:dp[ x+dix[r] ][ y+diy[r] ][k^1] >?< dp[x][y][r]+step[x][y][r][k]+1(比较同一位置,spfa的松弛操作);最后答案就在dp[TX][TY][K]中(K={0,1,2,3});

为什么要“^”呢?k={0,1,2,3}表示{上下左右},0^1=0(上变下),1^1=0(下变上),2^1=3(左变右),3^1=2(右变左)仔细想想就明白了。

这两个数组都可以用bfs来处理


代码如下:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define  inf  1e9
using namespace std;
struct node{int x,y;};
struct NODE{int x,y,r;};
int n,m,t,SX,SY,TX,TY,EX,EY;
int map[35][35],dis[35][35],vis[35][35][5];
int dp[35][35][5],step[35][35][5][5];
//dp[x][y][k]表示空白格到(x+dix[k],y+diy[k])的时间
//step[x][y][k][i]表示(x+dix[k],y+diy[k])到(x+dix[i],y+diy[i])的时间 
int dix[]={-1,1,0,0},diy[]={0,0,-1,1};
int bfs(int sx,int sy,int tx,int ty)
{
	if(!map[sx][sy])return inf;
	if(!map[tx][ty])return inf;
	for(int i = 1;i <= n;i++)
	for(int j = 1;j <= m;j++)
	dis[i][j] = inf;
	dis[sx][sy] = 0;
	queue <node> s;
	while(!s.empty())s.pop();
	s.push((node){sx,sy});
	while(!s.empty())
	{
		if(dis[tx][ty]!=inf)return dis[tx][ty];
		int x = s.front().x;
		int y = s.front().y;
		s.pop();
		for(int i = 0;i < 4;i++)
		{
			int xx = x + dix[i];
			int yy = y + diy[i];
			if(xx>0&&yy>0&&xx<=n&&yy<=m&&map[xx][yy]&&dis[xx][yy]==inf)
			{
				dis[xx][yy] = dis[x][y] + 1;
				s.push((node){xx,yy}); 
			}
		}
	}
	return inf;
}
void pre_bfs()
{
	for(int i = 1;i <= n;i++)
	for(int j = 1;j <= m;j++)
	{
		int c = map[i][j];
		map[i][j] = 0;//使BFS过程中不会走回(i,j); 
		for(int k = 0;k < 4;k++)
		for(int l = 0;l < 4;l++)
		step[i][j][k][l] = bfs(i+dix[k],j+diy[k],i+dix[l],j+diy[l]);
		map[i][j] = c;
	}
}

int spfa()
{
	queue <NODE>s;
	while(!s.empty())s.pop();
	for(int k = 0;k < 4;k++)
	if(dp[SX][SY][k]!=inf)
	{
		s.push((NODE){SX,SY,k});
		vis[SX][SY][k] = 1;
	}
	while(!s.empty())
	{
		int x = s.front().x;
		int y = s.front().y;
		int r = s.front().r;
		s.pop();vis[x][y][r] = 0;
		for(int k = 0;k < 4;k++)
		{
			int xx = x + dix[k];
			int yy = y + diy[k];
			if(xx>0&&yy>0&&xx<=n&&yy<=m&&map[xx][yy]&&step[x][y][r][k]!=inf)
			if(dp[xx][yy][k^1] > dp[x][y][r]+step[x][y][r][k]+1)//松弛操作 
			{
				dp[xx][yy][k^1] = dp[x][y][r]+step[x][y][r][k]+1;
				if(!vis[xx][yy][k^1])
				{
					s.push((NODE){xx,yy,k^1});
					vis[xx][yy][k^1] = 1;
				}
			}
		}
	}
	int ans = inf;
	for(int k = 0;k < 4;k++)
	ans = min(ans,dp[TX][TY][k]);
	if(ans == inf)return -1;
	return ans;
}

int Ans_get()
{
	scanf("%d%d%d%d%d%d",&EX,&EY,&SX,&SY,&TX,&TY);
	if(SX==TX && SY == TY)return 0;
	if(SX==EX && SY == EY)return -1;
	if(SX<=0||SY<=0||SX>n||SY>m)return -1;
	if(TX<=0||TY<=0||TX>n||TY>m)return -1;
	if(EX<=0||EY<=0||EX>n||EY>m)return -1;
	if(!map[SX][SY]||!map[TX][TY]||!map[EX][EY])return -1;//判断是否符合条件 
	for(int i = 1;i <= n;i++)
	for(int j = 1;j <= m;j++)
	for(int k = 0;k <  4;k++)
	dp[i][j][k] = inf;
	map[SX][SY] = 0;
	for(int k = 0;k < 4;k++)
	dp[SX][SY][k] = bfs(EX,EY,SX+dix[k],SY+diy[k]);//空白块到S旁边的时间 
	map[SX][SY] = 1;
	return spfa();
}

int main()
{
	freopen("puzzle.in","r",stdin);
	freopen("puzzle.out","w",stdout);
	scanf("%d%d%d",&n,&m,&t);
	for(int i = 1;i <= n;i++)
	for(int j = 1;j <= m;j++)
	scanf("%d",&map[i][j]);
	pre_bfs();
	for(int i = 1;i <= t;i++)
	printf("%d\n",Ans_get());
	return 0;
}

刷题感悟:

看别人的题解,心里很不爽委屈,最终还是看懂了。。。

涨姿势:1.  s.push((node){...})这个以前不会;

             2.  很巧妙这个方法,做题要注意题目给出的东西,起点终点通过什么,在这几个元素里面做文章,一定是这样!

还有6个月就要NOIP了,做几道题积累一下经验~大笑


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值