洛谷P1979 华容道 题解

嘤嘤嘤泥古管理至今没给申题解
QAQ只有五天啦..

洛谷P1979 华容道 题解

距离NOIP2018还有7天qwq写篇题解积攒下人品
(第一次写紫题题解还有些小激动)

一些说明

1、本题解与代码中的0/1/2/3分别代表上/下/左/右

2、为了清晰表述,题解中记目标棋子(题目中的 \(SX,SY\) )为 \(B\) ,目标格子(题目中的 \(TX,TY\) 为 )\(C\),空白格为\(E\)

【思路分析】
如几位dalao所说,这道题的建图就是把可行的状态连边,对于每次询问跑最短路即可。

我用 $ ok[i][j][0/1/2/3] $表示 \(B\)位于点\((i,j)\)时,四个方向是否可能有空白格(先不考虑每组询问中空白格是否可以到达,只考虑该格子是否为固定格,或越界。)

此部分代码如下:

//判断哪些状态是合法的 
    for(int i=1;i<=n;i++)//空白格在棋子上下左右 
    for(int j=1;j<=m;j++){
        if(!mapp[i][j])continue;//不可能有此状态 
        for(int k=0;k<4;k++)//四个方向 
        if(judge(i+xx[k],j+yy[k]))ok[i][j][k]=1;
        }

judge函数用来判断是否为空白格或越界:

bool judge(int ax,int ay){
    if(ax<=0||ax>n||ay<=0||ay>m)return 0;//边界 
    return mapp[ax][ay];
}

为了建图方便,我将状态存到一维数组里:
格子的编号:从上到下从左到右编

\(e.g.\)

  • 1 2 3 4
  • 5 6 7 8
  • ……

状态的编号:\(e.g.\)

1号格子的四个状态编号为0123

于是得到如下编号函数:

int getnum(int ax,int ay,int t){
    return ((ax-1)*m+ay)*4-(4-t);
}//t=0/1/2/3

由于\(q\)组询问的地图情况是相同的,我们可以先建图。

什么样的两个状态可以连边呢?

考虑两种情况:

·空白格子绕着\(B\)上下左右乱转

可以用bfs求出乱转最少多少次可以从上转到下,从左转到右...

需要注意的是,乱转时\(B\)是不能动的!!!(这个地方卡了好久QAQ)

·空白格子和\(B\)交换位置

必须保证两个状态都存在。

连上距离为1的边即可。

int bfs(int dx,int dy,int sx,int sy,int tx,int ty){//空白格子乱转的最小次数 
    //(sx,sy)出发到(tx,ty),不能经过(dx,dy)
    queue<white>q;
    memset(vis,0,sizeof vis);
    white st;st.x=sx;st.y=sy;
    st.step=0;vis[sx][sy]=1;
    q.push(st);
    while(!q.empty()){
        white noww=q.front();
        q.pop();
        if(noww.x==tx&&noww.y==ty)//到达目标格子 
        return noww.step;
        for(int i=0;i<4;i++){//四个方向乱转 
            if(judge(noww.x+xx[i],noww.y+yy[i])){//如果合法 
                if(vis[noww.x+xx[i]][noww.y+yy[i]])continue;//正在访问 
                if(noww.x+xx[i]==dx&&noww.y+yy[i]==dy)continue;//不能碰到目标棋子 
                white nxt;
                nxt.x=noww.x+xx[i];
                nxt.y=noww.y+yy[i];
                nxt.step=noww.step+1;
                q.push(nxt);vis[noww.x+xx[i]][noww.y+yy[i]]=1;
            }
        }
    }
    return inf;//到不了 
}
棋子不动,空白格乱转的情况 :
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    for(int k=0;k<4;k++)
    for(int l=k+1;l<4;l++){
        if(ok[i][j][k]&&ok[i][j][l]){//必须都要合法 
            int aa=getnum(i,j,k);
            int bb=getnum(i,j,l);
            int cc=bfs(i,j,i+xx[k],j+yy[k],i+xx[l],j+yy[l]);
            if(cc==inf)continue;
            add(aa,bb,cc);add(bb,aa,cc);
        }
    }

qwq

    for(int i=1;i<=n;i++)//空白与目标棋子左右互换 
    for(int j=1;j<m;j++){
        if(ok[i][j][3]&&ok[i][j+1][2]){ //注意谁左谁右
            int aa=getnum(i,j,3);//别问我怎么知道的orz
            int bb=getnum(i,j+1,2);
            add(aa,bb,1);add(bb,aa,1);
        }
    }
    for(int i=1;i<n;i++)//上下互换 
    for(int j=1;j<=m;j++){
        if(ok[i][j][1]&&ok[i+1][j][0]){
            int aa=getnum(i,j,1);
            int bb=getnum(i+1,j,0);
            add(aa,bb,1);add(bb,aa,1);
        }
    }

这样我们的初始化就完成啦!
(加边的操作就和普通图论一样了w)

对于每一组询问,先特判一下:
while(q--){
        scanf("%d%d%d%d%d%d",&ex,&ey,&bx,&by,&cx,&cy);
        if(bx==cx&&by==cy){
            puts("0");
            continue;
        }
        if(!mapp[cx][cy]){
            puts("-1");
            continue;
        }
        if(!mapp[bx][by]){
            puts("-1");
            continue;
        }
        work();
    }
然后考虑,怎样把一开始的状态转移到图上呢?

因为图上只存在空白格与\(B\) 相邻的状况,so暴力尝试让空白格跑到\(B\)的四个方向就好啦

    for(int i=0;i<4;i++){//空白走到目标棋子旁边 
        if(judge(bx+xx[i],by+yy[i])){//这个点可以走 
            int nw=bfs(bx,by,ex,ey,bx+xx[i],by+yy[i]);
            if(nw==inf)continue;//走不到 
            int nq=getnum(bx,by,i);
            d[nq]=nw;
            Q.push(nq);
            viss[nq]=1;
        }
    }
然后,愉快地跑SPFA
    while(!Q.empty()){
        int noww=Q.front();Q.pop();viss[noww]=0;
        for(int j=head[noww];j;j=b[j].nxt){
            int vv=b[j].to;
            if(d[vv]>d[noww]+b[j].dis){
                d[vv]=d[noww]+b[j].dis;
                if(!viss[vv])Q.push(vv),viss[vv]=1;
            }
        }
    }
最后,愉快地检查是否能到\(C\)
    int ans=inf;
    for(int i=0;i<4;i++){
        int qaq=getnum(cx,cy,i);
        ans=min(ans,d[qaq]);
    }
    if(ans==inf)puts("-1");
    else printf("%d\n",ans);

贴无注释代码QWQ

#include<bits/stdc++.h>
using namespace std;
const int MAXN=35;
const int inf=99999999;
int n,m,q,ex,ey,bx,by,cx,cy;
bool mapp[MAXN][MAXN];
int xx[4]={-1,1,0,0};
int yy[4]={0,0,-1,1};
int getnum(int ax,int ay,int t){
    return ((ax-1)*m+ay)*4-(4-t);
} 
struct white{
    int x,y;
    int step;
};
bool judge(int ax,int ay){
    if(ax<=0||ax>n||ay<=0||ay>m)return 0;
    return mapp[ax][ay];
}
bool vis[MAXN][MAXN];
int bfs(int dx,int dy,int sx,int sy,int tx,int ty){
    queue<white>q;
    memset(vis,0,sizeof vis);
    white st;st.x=sx;st.y=sy;
    st.step=0;vis[sx][sy]=1;
    q.push(st);
    while(!q.empty()){
        white noww=q.front();
        q.pop();
        if(noww.x==tx&&noww.y==ty)
        return noww.step;
        for(int i=0;i<4;i++){
            if(judge(noww.x+xx[i],noww.y+yy[i])){
                if(vis[noww.x+xx[i]][noww.y+yy[i]])continue;
                if(noww.x+xx[i]==dx&&noww.y+yy[i]==dy)continue; 
                white nxt;
                nxt.x=noww.x+xx[i];
                nxt.y=noww.y+yy[i];
                nxt.step=noww.step+1;
                q.push(nxt);vis[noww.x+xx[i]][noww.y+yy[i]]=1;
            }
        }
    }
    return inf;
}
struct edge{
    int to,nxt,dis;
}b[5005];
int head[5005],tot;
void add(int u,int v,int w){
    b[++tot].to=v;b[tot].nxt=head[u];
    b[tot].dis=w;head[u]=tot;
}
bool ok[MAXN][MAXN][5];
void init(){
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
        if(!mapp[i][j])continue;
        for(int k=0;k<4;k++)
        if(judge(i+xx[k],j+yy[k]))ok[i][j][k]=1;
        }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    for(int k=0;k<4;k++)
    for(int l=k+1;l<4;l++){
        if(ok[i][j][k]&&ok[i][j][l]){
            int aa=getnum(i,j,k);
            int bb=getnum(i,j,l);
            int cc=bfs(i,j,i+xx[k],j+yy[k],i+xx[l],j+yy[l]);
            if(cc==inf)continue;
            add(aa,bb,cc);add(bb,aa,cc);
        }
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<m;j++){
        if(ok[i][j][3]&&ok[i][j+1][2]){ 
            int aa=getnum(i,j,3);
            int bb=getnum(i,j+1,2);
            add(aa,bb,1);add(bb,aa,1);
        }
    }
    for(int i=1;i<n;i++)
    for(int j=1;j<=m;j++){
        if(ok[i][j][1]&&ok[i+1][j][0]){
            int aa=getnum(i,j,1);
            int bb=getnum(i+1,j,0);
            add(aa,bb,1);add(bb,aa,1);
        }
    }
}
queue<int>Q;
bool viss[5005];
int d[5005];
void work(){
    memset(d,128/3,sizeof d);
    memset(viss,0,sizeof viss);
    for(int i=0;i<4;i++){
        if(judge(bx+xx[i],by+yy[i])){
            int nw=bfs(bx,by,ex,ey,bx+xx[i],by+yy[i]);
            if(nw==inf)continue; 
            int nq=getnum(bx,by,i);
            d[nq]=nw;
            Q.push(nq);
            viss[nq]=1;
        }
    }
    while(!Q.empty()){
        int noww=Q.front();Q.pop();viss[noww]=0;
        for(int j=head[noww];j;j=b[j].nxt){
            int vv=b[j].to;
            if(d[vv]>d[noww]+b[j].dis){
                d[vv]=d[noww]+b[j].dis;
                if(!viss[vv])Q.push(vv),viss[vv]=1;
            }
        }
    }
    int ans=inf;
    for(int i=0;i<4;i++){
        int qaq=getnum(cx,cy,i);
        ans=min(ans,d[qaq]);
    }
    if(ans==inf)puts("-1");
    else printf("%d\n",ans);
}
int main(){
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)cin>>mapp[i][j];
    init();
    while(q--){
        scanf("%d%d%d%d%d%d",&ex,&ey,&bx,&by,&cx,&cy);
        if(bx==cx&&by==cy){
            puts("0");
            continue;
        }
        if(!mapp[cx][cy]){
            puts("-1");
            continue;
        }
        if(!mapp[bx][by]){
            puts("-1");
            continue;
        }
        work();
    }
    return 0;
}

完结撒花!!!

by Erutsiom

转载于:https://www.cnblogs.com/erutsiom/p/9904567.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值