洛谷 P1979 华容道

题目描述

【问题描述】

小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次。于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少需要多少时间。

小 B 玩的华容道与经典的华容道游戏略有不同,游戏规则是这样的:

  1. 在一个 n*m 棋盘上有 n*m 个格子,其中有且只有一个格子是空白的,其余 n*m-1个格子上每个格子上有一个棋子,每个棋子的大小都是 1*1 的;

  2. 有些棋子是固定的,有些棋子则是可以移动的;

  3. 任何与空白的格子相邻(有公共的边)的格子上的棋子都可以移动到空白格子上。

游戏的目的是把某个指定位置可以活动的棋子移动到目标位置。

给定一个棋盘,游戏可以玩 q 次,当然,每次棋盘上固定的格子是不会变的, 但是棋盘上空白的格子的初始位置、 指定的可移动的棋子的初始位置和目标位置却可能不同。第 i 次

玩的时候, 空白的格子在第 EXi 行第 EYi 列,指定的可移动棋子的初始位置为第 SXi 行第 SYi列,目标位置为第 TXi 行第 TYi 列。

假设小 B 每秒钟能进行一次移动棋子的操作,而其他操作的时间都可以忽略不计。请你告诉小 B 每一次游戏所需要的最少时间,或者告诉他不可能完成游戏。

输入输出格式

输入格式:

输入文件为 puzzle.in。

第一行有 3 个整数,每两个整数之间用一个空格隔开,依次表示 n、m 和 q;

接下来的 n 行描述一个 n*m 的棋盘,每行有 m 个整数,每两个整数之间用一个空格隔开,每个整数描述棋盘上一个格子的状态,0 表示该格子上的棋子是固定的,1 表示该格子上的棋子可以移动或者该格子是空白的。接下来的 q 行,每行包含 6 个整数依次是 EXi、EYi、SXi、SYi、TXi、TYi,每两个整数之间用一个空格隔开,表示每次游戏空白格子的位置,指定棋子的初始位置和目标位置。

输出格式:

输出文件名为 puzzle.out。

输出有 q 行,每行包含 1 个整数,表示每次游戏所需要的最少时间,如果某次游戏无法完成目标则输出−1。

输入输出样例

输入样例#1:
3 4 2
0 1 1 1
0 1 1 0
0 1 0 0
3 2 1 2 2 2
1 2 2 2 3 2
输出样例#1:
2
-1

说明

【输入输出样例说明】

棋盘上划叉的格子是固定的,红色格子是目标位置,圆圈表示棋子,其中绿色圆圈表示目标棋子。

  1. 第一次游戏,空白格子的初始位置是 (3, 2)(图中空白所示),游戏的目标是将初始位置在(1, 2)上的棋子(图中绿色圆圈所代表的棋子)移动到目标位置(2, 2)(图中红色的格子)上。

移动过程如下:

  1. 第二次游戏,空白格子的初始位置是(1, 2)(图中空白所示),游戏的目标是将初始位置在(2, 2)上的棋子(图中绿色圆圈所示)移动到目标位置 (3, 2)上。

要将指定块移入目标位置,必须先将空白块移入目标位置,空白块要移动到目标位置,必然是从位置(2, 2)上与当前图中目标位置上的棋子交换位置,之后能与空白块交换位置的只有当前图中目标位置上的那个棋子,因此目标棋子永远无法走到它的目标位置, 游戏无

法完成。

【数据范围】

对于 30%的数据,1 ≤ n, m ≤ 10,q = 1;

对于 60%的数据,1 ≤ n, m ≤ 30,q ≤ 10;

对于 100%的数据,1 ≤ n, m ≤ 30,q ≤ 500。


70分暴力

第一想法:哈,不会做。

然而,数据有梯度,不要放弃梦想。

我们可以开一个结构体,存储需要移动的棋子位置和空格的位置,只有两种更新方法:

  1. 移动空格,空格不与需要移动的棋子重合。
  2. 空格移动一步可以到达需要移动的棋子,两者交换位置。

然而第一次,忘了每次清空队列,只有30分。

太暴力了,但可以拿70分(高性价比)。


#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
int n,m,que,ex,ey,sx,sy,tx,ty,xx[]={0,0,1,0,-1},yy[]={0,-1,0,1,0};
bool b[35][35],vis[35][35][35][35];
struct node
{
	int x1,y1,x2,y2,dis;
}tmp;
queue<node>q;
void bfs()
{
	while(!q.empty())
		q.pop();
	tmp.x1=sx,tmp.y1=sy,tmp.x2=ex,tmp.y2=ey,tmp.dis=0;
	vis[sx][sy][ex][ey]=1;
	q.push(tmp);
	while(!q.empty())
	{
		node u=q.front();
		q.pop();
		if(u.x1==tx&&u.y1==ty)
		{
			printf("%d\n",u.dis);
			return ;
		}
		for(int i=1;i<=4;i++)//移动空格 
			if(u.x2+xx[i]>=1&&u.x2+xx[i]<=n&&u.y2+yy[i]>=1&&u.y2+yy[i]<=m&&!vis[u.x1][u.y1][u.x2+xx[i]][u.y2+yy[i]]&&!b[u.x2+xx[i]][u.y2+yy[i]]&&(u.x1!=u.x2+xx[i]||u.y1!=u.y2+yy[i]))
			{
				tmp=u;
				tmp.x2+=xx[i],tmp.y2+=yy[i],tmp.dis++;
				vis[tmp.x1][tmp.y1][tmp.x2][tmp.y2]=1;
				q.push(tmp);
			}
		for(int i=1;i<=4;i++)//交换 
			if(u.x1+xx[i]==u.x2&&u.y1+yy[i]==u.y2&&!vis[u.x2][u.y2][u.x1][u.y1])
			{
				tmp=u;
				swap(tmp.x1,tmp.x2),swap(tmp.y1,tmp.y2);
				tmp.dis++;
				vis[tmp.x1][tmp.y1][tmp.x2][tmp.y2]=1;
				q.push(tmp);
			}
	}
	printf("-1\n");
	return ;
}
int main()
{
	scanf("%d%d%d",&n,&m,&que);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			int x;
			scanf("%d",&x);
			if(x==0)
				b[i][j]=1;
		}
	while(que--)
	{
		scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
		memset(vis,0,sizeof(vis));
		bfs();
	}
	return 0;
}


正解


下面说正解:

对于70分的算法,如果q比较大,那么就会跑许多遍bfs,会有许多冗余计算。
而且bfs时,状态也是很冗余的,有用的状态只有空格和需要移动的方格相邻的情况。
那么就把这些状态处理出来,在状态之间连边,每次就相当于求最短路了。
对于状态,可以一一编号。
只有两种更新方法:
  1. 移动空格,空格不与需要移动的棋子重合。
  2. 空格移动一步可以到达需要移动的棋子,两者交换位置
对于1可以bfs更新,对于2,模拟吧。
最后注意两点:
  1. 注意判断s==t的情况,会坑35分。
  2. 数组大小一定要开够,对于新建的图,需要n*m*4+2*q各点,n*m*(4*4+4)+2*4*q条边。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
int n,m,que,mcnt,cnt,hd[4605],mp[35][35],id[35][35][5],dis[4605],xx[]={0,0,1,0,-1},yy[]={0,-1,0,1,0};
bool vis[35][35],inq[4605];
struct edge
{
    int nxt,to,val;
}v[22005];
struct node
{
    int x,y,dis;
}tmp;
queue<node>q1;
queue<int>q2;
void addedge(int x,int y,int z)
{
    ++cnt;
    v[cnt].to=y;
    v[cnt].nxt=hd[x];
    v[cnt].val=z;
    hd[x]=cnt;
}
bool pd(int x,int y)
{
    if(x>=1&&x<=n&&y>=1&&y<=m&&mp[x][y]==1)
        return 1;
    return 0;
}
int bfs(int banx,int bany,int sx,int sy,int tx,int ty)
{
    while(!q1.empty())
        q1.pop();
    memset(vis,0,sizeof(vis));
    tmp.x=sx,tmp.y=sy,tmp.dis=0;
    vis[sx][sy]=1,vis[banx][bany]=1;
    q1.push(tmp);
    while(!q1.empty())
    {
        node u=q1.front();
        q1.pop();
        if(u.x==tx&&u.y==ty)
            return u.dis;
        for(int i=1;i<=4;i++)
            if(pd(u.x+xx[i],u.y+yy[i])&&!vis[u.x+xx[i]][u.y+yy[i]])
            {
                tmp=u;
                tmp.x+=xx[i],tmp.y+=yy[i],tmp.dis++;
                vis[tmp.x][tmp.y]=1;
                q1.push(tmp);
            }
    }
    return 1e9+11;
}
void spfa(int S,int T)
{
    memset(dis,0x7f,sizeof(dis));
    memset(inq,0,sizeof(inq));
    inq[S]=1;
    dis[S]=0;
    q2.push(S);
    while(!q2.empty())
    {
        int u=q2.front();
        q2.pop();
        inq[u]=0;
        for(int i=hd[u];i;i=v[i].nxt)
            if(dis[v[i].to]>dis[u]+v[i].val)
            {
                dis[v[i].to]=dis[u]+v[i].val;
                if(!inq[v[i].to])
                {
                    inq[v[i].to]=1;
                    q2.push(v[i].to);
                }
            }
    }
    if(dis[T]>1e9+7)
        printf("-1\n");
    else
        printf("%d\n",dis[T]);
}
int main()
{
    scanf("%d%d%d",&n,&m,&que);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&mp[i][j]);
    //编号 
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=1;k<=4;k++)
                id[i][j][k]=++mcnt;
    //预处理(x,y)从k方向移到g方向连边 
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(mp[i][j])
                for(int k=1;k<=4;k++)
                    if(pd(i+xx[k],j+yy[k]))
                        for(int g=1;g<=4;g++)
                            if(k!=g&&pd(i+xx[g],j+yy[g]))
                            {
                                int t=bfs(i,j,i+xx[k],j+yy[k],i+xx[g],j+yy[g]);
                                if(t<=1e9+7)
                                    addedge(id[i][j][k],id[i][j][g],t);
                            }
    //预处理(x,y) 在k方向交换,连边 
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(mp[i][j])
                for(int k=1;k<=4;k++)
                    if(pd(i+xx[k],j+yy[k]))
                        addedge(id[i][j][k],id[i+xx[k]][j+yy[k]][(k+1)%4+1],1);
    while(que--)
    {
        int ex,ey,sx,sy,tx,ty;
        scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
        if(sx==tx&&sy==ty)
        {
            printf("0\n");
            continue;
        }
        int S=++mcnt,T=++mcnt;
        for(int i=1;i<=4;i++)
            if(pd(sx+xx[i],sy+yy[i]))
                addedge(S,id[sx][sy][i],bfs(sx,sy,ex,ey,sx+xx[i],sy+yy[i]));
        for(int i=1;i<=4;i++)
            if(pd(tx+xx[i],ty+yy[i]))
                addedge(id[tx][ty][i],T,0);
        spfa(S,T);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值