【洛谷 P1979】[NOIP2013 提高组] 华容道

17 篇文章 0 订阅

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


解题思路

刚读完题的表情是这样的:(ಥ﹏ಥ)
(毕竟总有些分是我不配拥有的TOT)

对于移动时的状态,分为有用状态和后继状态:

  • 有用状态:空格子在指定棋子的上下左右。
  • 后继状态:
    (1)空格子还是在棋子的上下左右,也就是空格子围着指定格子转的另外3个状态,这个步数用bfs计算。
    (2)空格子与指定棋子交换了位置。步数为1。

状态怎么记录?:用0,1,2,3分别表示空白格在指定格的上右下左(顺时针),然后给棋盘重新标号
状态 = ((行号 − 1 ) ∗ 列数 + (列号 − 1 )) ∗ 4 + 0 / 1 / 2 / 3 状态=((行号-1)*列数+(列号-1))*4+ 0/1/2/3 状态=((行号1列数+(列号1))4+0/1/2/3

// 空白格也可以理解为一个能在1的区域随意走动的棋子,因为别的棋子移动到空白格,相当于空白格移动到那个棋子的位置

总体思路
第一步,抽离有用状态
第二步,有用状态与有用的后继状态 连边构图,
(边权为:由有用状态 到 后继状态 所需的最小步数)
第三步,初始状态 到 目标状态 ,跑最短路(最好是跑spfa)

STEP 1:
因为q个提问下图都是一样的(输入的1,0图),所以可以枚举一个固定格,再枚举其上,右,下,左作为空白格(这里的上,右,下,左为原状态),bfs当前空白格移动到每个点所需要的步数。

void bfs(int px,int py,int ex,int ey,int d){
//d为0/1/2/3,表示状态
	memset(pre,-1,sizeof(pre));//pre数组表示啥呢??? 其实简单来说就是:固定指定块,空白块在指定块的其中一个方向然后再由这个状态到其他状态的最短路
	pre[px][py]=1;
	pre[ex][ey]=0;
	c x;
	x.x=ex,x.y=ey;
	q.push(x);
	while(!q.empty())
	{
		x=q.front();//cout<<x.x;
		q.pop();
		for(int i=0;i<4;i++)
		{
			int xx=x.x+dx[i],yy=x.y+dy[i];
			if(pre[xx][yy]==-1&&a[xx][yy])
			{
				c nxt;
				nxt.x=xx,nxt.y=yy;
				q.push(nxt);
				pre[xx][yy]=pre[x.x][x.y]+1;
			}
		}
	}

for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(a[i][j]==1)
			{
				if(a[i-1][j])
					bfs(i,j,i-1,j,0);					
				if(a[i][j+1])
					bfs(i,j,i,j+1,1);					
				if(a[i+1][j])
					bfs(i,j,i+1,j,2);
				if(a[i][j-1])
					bfs(i,j,i,j-1,3);
			}
		}
	}	

STEP 2:
通过StEP 1,可以得出空格子围着指定格子转到另外3个状态 (后继状态1))所需要的步数。将原状态与后继状态相连(建图),边权为步数。

x.x=px,x.y=py;
	int tmp=number(px,py);
	for(int i=0;i<4;i++)
	{
		int xx=x.x+dx[i],yy=x.y+dy[i];
		if(pre[xx][yy]>0)
		{
			add(tmp+d,tmp+i,pre[xx][yy]);
		}
	}
	add(tmp+d,number(ex,ey)+(d+2)%4,1);//最后一种后继状态是指定格与空白格交换位置,因为规定的0123是上右下左,所以(d+2)%4可以直接转化为对面的方向交换相当于走一步,所以边权为1 
}

注意最后那个add是下图中的红边
在这里插入图片描述

STEP 3:

先对于每种问题bfs,求出对应空白格的pre,

终点状态是什么?就是sx,sy走到tx,ty,此时必有空格子在tx,ty四周。

所以我们现在要求的就转化成了空格子先到指定格子四周,再到目标格子四周的最短路。

空格子到指定格子四周的步数在bfs中已经得到。(bfs得到了空格子从指定格子旁到任意格子的最短步数)

空格子到目标格子四周用SPFA解决,起点为指定格子
,图已经在STEP 2中建好,这个图为所有有用状态相连,所以我们有空格子绕格子走动的所有路径和边权。

void spfa(int x,int y){
    memset(v,0,sizeof(v));
	memset(dis,-1,sizeof(dis));
	for(int i=0;i<4;i++)
	{
		int xx=x+dx[i],yy=y+dy[i]; 
		if(pre[xx][yy]!=-1)
		{
			int tmp=number(x,y)+i;	
			dis[tmp]=pre[xx][yy];//到每个状态间的最短路
			mp.push(tmp);
		}
	}
	while(!mp.empty())
	{
		int x1=mp.front();
		mp.pop();
		v[x1]=0;
		for(int i=head[x1];i;i=s[i].next){
		
			int y2=s[i].x;	
			if(dis[y2]==-1||dis[y2]>dis[x1]+s[i].w)
			{
				dis[y2]=dis[x1]+s[i].w;
				if(!v[y2])
				{
					v[y2]=1;
					mp.push(y2);
				}
			}
		}
	}
}

最后统计答案要注意,得到的dis为到每个状态间的最短路,所以要枚举空白格在指定目标的上下左右, a n s = m i n ( d i s [ 空白格在 t x , t y 旁的状态 ] ) ans=min(dis[空白格在tx,ty旁的状态]) ans=min(dis[空白格在tx,ty旁的状态])

int tmp=number(tx,ty);
        for(int j=0;j<4;j++)
        {///+j是空白块的状态方向
			if(dis[tmp+j]!=-1)
				ans=min(ans,dis[tmp+j]);
        }

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN=3610*5,INF=1e9;
const int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
int n,m,Q,k,ans,ex,ey,tx,ty,sx,sy;
int pre[40][40],a[40][40],dis[MAXN],v[MAXN],head[MAXN];

struct cc{
	int x,next,w;
}s[MAXN];

struct c{
	int x,y;
};

void add(int x,int y,int w){
	s[++k].x=y;
	s[k].w=w;
	s[k].next=head[x];
	head[x]=k;
}

queue<c>q;
queue<int>mp;

int number(int x,int y){
	y--;
	return ((x-1)*m+y)<<2;
}

void bfs(int px,int py,int ex,int ey,int d){
	memset(pre,-1,sizeof(pre));//pre数组表示啥呢??? 其实简单来说就是:固定指定块,空白块在指定块的其中一个方向然后再由这个状态到其他状态的最短路
	pre[px][py]=1;
	pre[ex][ey]=0;
	c x;
	x.x=ex,x.y=ey;
	q.push(x);
	while(!q.empty())
	{
		x=q.front();//cout<<x.x;
		q.pop();
		for(int i=0;i<4;i++)
		{
			int xx=x.x+dx[i],yy=x.y+dy[i];
			if(pre[xx][yy]==-1&&a[xx][yy])
			{
				c nxt;
				nxt.x=xx,nxt.y=yy;
				q.push(nxt);
				pre[xx][yy]=pre[x.x][x.y]+1;
			}
		}
	}
	if(d==8)return;
	x.x=px,x.y=py;
	int tmp=number(px,py);
	for(int i=0;i<4;i++)
	{
		int xx=x.x+dx[i],yy=x.y+dy[i];
		if(pre[xx][yy]>0)
		{
			add(tmp+d,tmp+i,pre[xx][yy]);
		}
	}
	add(tmp+d,number(ex,ey)+(d+2)%4,1);
}

void spfa(int x,int y){
    memset(v,0,sizeof(v));
	memset(dis,-1,sizeof(dis));
	for(int i=0;i<4;i++)
	{
		int xx=x+dx[i],yy=y+dy[i]; 
		if(pre[xx][yy]!=-1)
		{
			int tmp=number(x,y)+i;	
			dis[tmp]=pre[xx][yy];
			mp.push(tmp);
		}
	}
	while(!mp.empty())
	{
		int x1=mp.front();
		mp.pop();
		v[x1]=0;
		for(int i=head[x1];i;i=s[i].next){
		
			int y2=s[i].x;	
			if(dis[y2]==-1||dis[y2]>dis[x1]+s[i].w)
			{
				dis[y2]=dis[x1]+s[i].w;
				if(!v[y2])
				{
					v[y2]=1;
					mp.push(y2);
				}
			}
		}
	}
}


int main(){
	freopen("puzzle.in","r",stdin);
	freopen("puzzle.out","w",stdout);
	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 i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(a[i][j]==1)
			{
				if(a[i-1][j])
					bfs(i,j,i-1,j,0);					
				if(a[i][j+1])
					bfs(i,j,i,j+1,1);					
				if(a[i+1][j])
					bfs(i,j,i+1,j,2);
				if(a[i][j-1])
					bfs(i,j,i,j-1,3);
			}
		}
	}	
	for(int i=1;i<=Q;i++)
	{
		ans=INF;
		scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
	  	if(sx==tx && sy==ty) {
      		printf("0\n");
       		continue;
        }
        bfs(sx,sy,ex,ey,8);
        spfa(sx,sy);
		int tmp=number(tx,ty);
        for(int j=0;j<4;j++)
        {///+j是空白块的状态方向
			if(dis[tmp+j]!=-1)
				ans=min(ans,dis[tmp+j]);
        }
        if(ans==INF)
            ans=-1;
        printf("%d\n",ans); 
	}
}
/*
11
1 2 3 4 4 3 2 5 5 6 5
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值