题解 noip2013 华容道

题目链接
说在前面: 首先这道题比较冗杂,如果因为您不太接受我的码风/表达方式而带来了不太好的阅读效果,我向您道歉(鞠躬

题面请戳链接,请您在确保对这道题目的背景和做这道题的大致方向有了一定的掌握后,再食用这篇博客,以确保较好的阅读效果

除去多次询问不谈,这道题大致的框架是非常明显的搜索。但是虽然是多次询问,棋盘的状态是不会变的,并且只有部分的位置是真正有价值的。那么就可以考虑图论建模,在这些有价值的状态上跑从终态到末态的最短路。因为棋子要移动的时候,空白块一定要移到它的周围,所以我们可以为这种周围的位置编号。 f [ x ] [ y ] [ i ] f[x][y][i] f[x][y][i]表示在点 ( x , y ) (x, y) (x,y)的方向 i ( 0 ≤ i ≤ 3 ) i(0\le i \le 3) i(0i3)上的位置的编号。这里我们有两个数组表示方向的偏移量,不同的 i i i分别配对左、右、上、下: d x [ 4 ] = 0 , 0 , − 1 , 1 , d y [ 4 ] = − 1 , 1 , 0 , 0 dx[4]={0, 0, -1, 1},dy[4]={-1, 1, 0, 0} dx[4]=0,0,1,1,dy[4]=1,1,0,0。这样编号的好处是, i   x o r   1 i \ xor\ 1 i xor 1即可表示相反的方向

点建好了,接下来考虑怎么连边。显然的一点是,为了使目标棋子更快地前进,空白格移动到棋子旁的路上不应该与棋子发生交换。对于一个在棋子旁边的空白格,它有以下的选择:

  1. 绕到棋子的另一个方向上,使得棋子向该方向前进,即:$f[x][y][i]\to f[x][y][j](i\not= j) $
  2. 直接和棋子交换位置,让棋子向原本的空白格的方向前进,即: f [ x ] [ y ] [ i ] → f [ x + d x [ i ] ] [ y + d y [ i ] ] [ i   x o r   1 ] f[x][y][i]\to f[x+dx[i]][y+dy[i]][i \ xor \ 1] f[x][y][i]f[x+dx[i]][y+dy[i]][i xor 1]

由于第一种选择不能受到各种元素限制,所以需要bfs来确定边权。作完这些预处理之后,这些点之间就构成了一个图论模型。对于每次询问,对移动的棋子周围的4个位置做一次bfs,得到让空白格(在不移动该棋子的情况下)移动到棋子四周的最小代价。再把这四个初状态压入队列中,跑一遍 S P F A SPFA SPFA,最后枚举末状态取一个 d i s dis dis m i n min min就是答案了

还有一些细节:

  1. 起点和终点相同时记得特判
  2. 正确估计模型中最多会有多少个点来开数组

具体请结合代码理解:

//从main开始看
#include <cstdio>
#include <cstring>
const int maxn=50;
const int maxm=1000000;
const int INF=0x3f3f3f3f;
int a[maxn][maxn],f[maxn][maxn][4];
int dx[4]={0, 0, 1, -1},dy[4]={1, -1, 0, 0};
int head[maxn*maxn*4],to[maxm],nxt[maxm],val[maxm];
int dis[maxn*maxn];
int cnt,tot;
int n,m,Q;
bool vis[maxn][maxn],inq[maxn*maxn];
struct node
{
	int x,y,w;
	node() {}
	node(int a,int b,int c) {x=a,y=b,w=c;}
};
struct Queue
{
	int l,r;
	node a[maxn*maxn];
	Queue() {l=1,r=0;}
	void push(node x) {a[++r]=x;}
	void pop() {l++;}
	node front() {return a[l];}
	bool empty() {return l>r;}
};//这两个手写队列分别给bfs和SPFA用= =,直接用STL也差不多的
struct Que
{
	int l,r;
	int a[maxn*maxn];
	Que() {l=1,r=0;}
	void push(int x) {a[++r]=x;}
	void pop() {l++;}
	int front() {return a[l];}
	bool empty() {return l>r;}
};

int min(int x,int y) {return x<y?x:y;}
bool valid(int x,int y) {return a[x][y];}
void add(int u,int v,int w)
{
	nxt[++tot]=head[u];
	head[u]=tot;
	to[tot]=v;
	val[tot]=w;
}
int bfs(int target_x,int target_y,int sx,int sy,int tx,int ty)
{
	if (sx==tx&&sy==ty)
		return 0;
	memset(vis, 0, sizeof(vis));
	Queue q;
	vis[sx][sy]=1;
	vis[target_x][target_y]=1;//特殊标记一下,就当作走过了,反正不能走
	q.push(node(sx, sy, 0));
	while(!q.empty())
	{
		int ux=q.front().x,uy=q.front().y,uw=q.front().w;
		q.pop();
		for (int i=0;i<4;i++)
		{
			int vx=ux+dx[i],vy=uy+dy[i];
			if (!valid(vx, vy)||vis[vx][vy])
				continue;
			if (vx==tx&&vy==ty)
				return uw+1;
			vis[vx][vy]=1;
			q.push(node(vx, vy, uw+1));
		}
	}
	return INF;
}
int SPFA(int ex,int ey,int sx,int sy,int tx,int ty)
{
	if (sx==tx&&sy==ty)//特判一下
		return 0;
	memset(dis, 0x3f, sizeof(dis));
	memset(inq, 0, sizeof(inq));
	Que q;
	for (int i=0;i<4;i++)
	{
		int vx=sx+dx[i],vy=sy+dy[i];
		if (!valid(vx, vy))
			continue;
		dis[f[sx][sy][i]]=bfs(sx, sy, ex, ey, vx, vy);
		q.push(f[sx][sy][i]);
		inq[f[sx][sy][i]]=1;
	}
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		inq[u]=0;
		for (int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if (dis[v]>dis[u]+val[i])
			{
				dis[v]=dis[u]+val[i];
				if (!inq[v])
					inq[v]=1,q.push(v);
			}
		}
	}
	int ans=INF;
	for (int i=0;i<4;i++)
		ans=min(ans, dis[f[tx][ty][i]]); 
	return ans==INF?-1:ans;
}
int main()
{
	scanf("%d%d%d",&n,&m,&Q);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
			scanf("%d",&a[i][j]);
	
	for (int x=1;x<=n;x++)
		for (int y=1;y<=m;y++)
			for (int i=0;i<4;i++)
			{
				int vx=x+dx[i],vy=y+dy[i];
				if (!valid(vx, vy))
					continue;
				f[x][y][i]=++cnt;//给有用状态编号 
			}
			
	for (int x=1;x<=n;x++)
		for (int y=1;y<=m;y++)
			for (int i=0;i<4;i++)
				for (int j=0;j<4;j++)
				{
					if (i==j||!valid(x+dx[i], y+dy[i])||!valid(x+dx[j], y+dy[j]))
						continue;
					add(f[x][y][i], f[x][y][j], bfs(x, y, x+dx[i], y+dy[i], x+dx[j], y+dy[j]));
				}//选择1
				
	for (int x=1;x<=n;x++)
		for (int y=1;y<=m;y++)
			for (int i=0;i<4;i++)
			{
				if (!valid(x+dx[i], y+dy[i]))
					continue;
				add(f[x][y][i], f[x+dx[i]][y+dy[i]][i^1], 1);//选择2,交换空白格与棋子的位置,将这两个状态连边 
			}
	
	while(Q--)
	{
		int ex,ey,sx,sy,tx,ty;
		scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
		printf("%d\n",SPFA(ex, ey, sx, sy, tx, ty)); 
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值