NOIP2013 华容道(极难脑洞题)

题目描述太长了,我就不写了。
这是题目链接NOIP2013华容道

思路:
首先我不得不说一句,这是一道图论题,是的你没听错,图论题!!!但是这很明显是一道BFS好不!!!总之,这题需要用到SPFA。
但是你只要有超强的脑洞(对于我是不存在的),这道题就能很愉(bian)悦(tai)地解决了,可是这依然改变不了这是NOIP2013最恶心的题的事实。
首先我们发现一些细节问题:这题有多次询问,但是棋盘的格局是不变的;空白格子只有一个,目标棋子和目标位置也只有一个。那么我们就开始脑洞了。首先,我们发现目标棋子能移动当且仅当空白格子位于目标棋子的四周,这样我们就可以通过步数+1来移动目标棋子(实际上可以认为是目标棋子与空白格子交换位置),这样以后我们对整张棋盘进行预处理。令f[i][j][k][h]为目标棋子在坐标(i,j),空白格子在目标棋子的k方向上,转移到h方向上所需的最小步数,因为转移的过程中可能有障碍物,步数不能直接通过计算得出,所以我们要对每个点进行BFS。令d[i][j][k]表示目标棋子在(i,j),空白格子在k方向上的一个状态,并开一个结构体数组l,可以认为l是d的一个反向映射(我自己这么认为的,就是知道了一个确定的状态d[i][j][k],我们可以通过l数组的相应值得出当前d数组的i,j,k)。因为棋盘不变,所以预处理可以减少相当多的计算量,降低时间复杂度。
综上,我们需要两遍BFS,第一遍BFS对于每个点BFS,预处理出f[i][j][k][h]的值;第二遍BFS,把空白格子移动到目标棋子的四个方向上,各自记录一下状态及步数,并设置为最短路的初始状态。接下来我们就可以最短路乱搞了,把状态d当做点,把当前可到达的状态当做能通过边到达的点,把f数组当做边权,跑SPFA,就行了。
还有一个细节问题:当前点向四个方向移动时,可以证明,空白格子先转移到要移动方向的反方向再与目标棋子交换,这样一次移动步数和最小,所以我们可以处理边权问题。
PS:代码长到怀疑人生了。。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
using namespace std;
const int N=30+5;
const int M=10000+5;
int read()
{
    int ret=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')
    {ret=ret*10+ch-'0';ch=getchar();}
    return ret*f;
}
int n,m,mmp[N][N];
const int dx[]={0,1,0,-1,0};
const int dy[]={0,0,1,0,-1};
int f[N][N][5][5];
int d[N][N][5];
bool vis[N][N];
struct node{
    int x,y,k;
}l[M];
int dist[M],tot=0;
bool inq[M];
int fan[]={0,3,4,1,2};
struct zt{
    int x,y,step;
};
bool can(int x,int y)
{
     if(x>0&&x<=n&&y>0&&y<=m&&mmp[x][y]) return 1;
     return 0;
}
queue<zt> q1;
queue<int> q2;
void init()
{
    while(!q1.empty()) q1.pop();
    memset(vis,0,sizeof(vis));
}
int bfs1(int x,int y,int k,int h)
{
    init();
    vis[x][y]=1;
    int sx=x+dx[k],sy=y+dy[k];
    int tx=x+dx[h],ty=y+dy[h];
    q1.push((zt){sx,sy,0});
    while(!q1.empty())
    {
        zt F=q1.front();
        q1.pop();
        if(F.x==tx&&F.y==ty) return F.step;
        for(int i=1;i<=4;i++)
        {
            zt T;
            T.x=F.x+dx[i],T.y=F.y+dy[i];
            if(can(T.x,T.y)&&!vis[T.x][T.y])
            {
                T.step=F.step+1;
                q1.push(T);
                vis[T.x][T.y]=1;
            }
        }
    }
    return -1;
}
void bfs2(int x,int y,int tx,int ty)
{
    init();
    vis[x][y]=1,vis[tx][ty]=1;
    q1.push((zt){x,y,0});
    while(!q1.empty())
    {
        zt F=q1.front();
        q1.pop();
        for(int i=1;i<=4;i++)
        {
            if(tx+dx[i]==F.x&&ty+dy[i]==F.y)
            {
                int dd=d[tx][ty][i];
                q2.push(dd);
                inq[dd]=1;
                dist[dd]=F.step;
                break;
            }
        }
        for(int i=1;i<=4;i++)
        {
            zt T;
            T.x=F.x+dx[i],T.y=F.y+dy[i];
            if(can(T.x,T.y)&&!vis[T.x][T.y])
            {
                T.step=F.step+1;
                q1.push(T);
                vis[T.x][T.y]=1;
            }
        }
    }
}
void make_f()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(mmp[i][j])
            {
                for(int k=1;k<=4;k++)
                {
                    if(can(i+dx[k],j+dy[k]))
                    {
                        for(int h=1;h<=4;h++)
                        {
                            if(can(i+dx[h],j+dy[h]))
                            {
                                int w=bfs1(i,j,k,h);
                                if(w!=-1) f[i][j][k][h]=w;
                            }
                        }
                    }
                }
            }
        }
}
void make_l()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(mmp[i][j])
            {
                for(int k=1;k<=4;k++)
                {
                    d[i][j][k]=++tot;
                    l[tot].x=i;
                    l[tot].y=j;
                    l[tot].k=k;
                }
            }
        }
}
int spfa(int tx,int ty)
{
    while(!q2.empty())
    {
        int u=q2.front();
        q2.pop();
        inq[u]=0;
        int x=l[u].x,y=l[u].y,k=l[u].k;
        for(int i=1;i<=4;i++)
        {
            int zx=x+dx[i],zy=y+dy[i];
            if(can(zx,zy))
            {
                int v=d[zx][zy][fan[i]];
                if(dist[v]>dist[u]+f[x][y][k][i]+1)
                {
                    dist[v]=dist[u]+f[x][y][k][i]+1;
                    if(!inq[v])
                    {
                        q2.push(v);
                        inq[v]=1;
                    }
                }
            }
        }
    }
    int ans=0x3fffffff;
    for(int i=1;i<=4;i++) ans=min(ans,dist[d[tx][ty][i]]);
    if(ans>1e9) ans=-1;
    return ans;
}
int main()
{
    int Z;
    scanf("%d%d%d",&n,&m,&Z);
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            mmp[i][j]=read();
    make_f();
    make_l();
    while(Z--)
    {
        int EX,EY,SX,SY,TX,TY;
        EX=read(),EY=read(),SX=read(),SY=read(),TX=read(),TY=read();
        memset(dist,0x3f,sizeof(dist));
        bfs2(EX,EY,SX,SY);
        if(SX==TX&&SY==TY) printf("0\n");
        else printf("%d\n",spfa(TX,TY));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值