【NOIP2013/Luogu1979】华容道 最短路

原题走这里

一看到这道题,
首先可以用脚指头想到,
既然目标是让指定棋子移到目标位置上,
那么决定棋盘状态的就只有指定棋子的位置和空格的位置两个因素。

接着,可以用手指头想到,
既然,指定格子只有在周围有空格时才能动,
那么实际上状态就只有棋子的位置,以及空格在棋子的哪个方向。
4nm 4 n m 个,对每个状态建点。

接着我们会发现,对于一个棋盘状态,有两种转移:
1.让棋子移到空格,代价为1
2.让空格到处跑,跑到棋子的另一个方向去,代价可以用bfs暴力算出
两种转移分别建边,SPFA即可。

注意:SPFA一开始,要算出空格从初始位置移到棋子四周的代价,作为初始代价。

个人认为是一道很好很难的最短路题。

具体实现见代码如下:

#include <iostream>
#include <cstring>
#define INF 0x3f3f3f3f
using namespace std;
int d[4][2]= {{1,0},{-1,0},{0,1},{0,-1}},sx,sy,tx,ty,ex,ey,cost[35][31][4][4],dis[35][35][4],n,m,l,a[35][35];
bool vis[35][35],b[35][35][4];
struct data {
    int x,y,k;
} q[1000000];
int bfs(int _ex,int _ey,int _sx,int _sy,int k) {
    memset(vis,0,sizeof(vis));
    int head=1,tail=0;
    vis[_sx][_sy]=1;
    vis[_ex][_ey]=1;
    q[++tail]=(data) {
        _ex,_ey,0
    };
    while(tail>=head) {
        int x=q[head].x,y=q[head].y,s=q[head].k;
        if((_sx+d[k][0]==x)&&(_sy+d[k][1]==y)) {
            return s;
        }
        for(int i=0; i<4; i++) {
            if(a[x+d[i][0]][y+d[i][1]]&&(!vis[x+d[i][0]][y+d[i][1]])) {
                q[++tail]=(data) {
                    x+d[i][0],y+d[i][1],s+1
                };
                vis[x+d[i][0]][y+d[i][1]]=1;
            }
        }
        head++;
    }
    return INF;
}
void pre() {
    memset(cost,0x3f,sizeof(cost));
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            if(a[i][j]) {
                for(int di=0; di^4; di++) {
                    if(a[i+d[di][0]][j+d[di][1]]) {
                        cost[i][j][di][di]=0;
                        for(int dj=0; dj^di; dj++) {
                            if(a[i+d[dj][0]][j+d[dj][1]]) {
                                cost[i][j][di][dj]=cost[i][j][dj][di]=bfs(i+d[di][0],j+d[di][1],i,j,dj);
                            }
                        }
                    }
                }
            }
        }
    }
}
int spfa() {
    memset(dis,0x3f,sizeof(dis));
    for(int i=0; i<4; i++) {
        dis[sx][sy][i]=bfs(ex,ey,sx,sy,i);
    }
    memset(b,0,sizeof(b));
    int head=1,tail=0;
    for(int i=0; i<4; i++) {
        q[++tail]=(data) {
            sx,sy,i
        };
        b[sx][sy][i]=1;
    }
    while(tail>=head) {
        int x=q[head].x,y=q[head].y,k=q[head].k;
        b[x][y][k]=0;
        head++;
        int vx=x+d[k][0],vy=y+d[k][1];
        for(int i=0; i<4; i++) {
            if(a[vx][vy]) {
                if(dis[x][y][k]+cost[vx][vy][k^1][i]+1<dis[vx][vy][i]) {
                    dis[vx][vy][i]=dis[x][y][k]+cost[vx][vy][k^1][i]+1;
                    if(!b[vx][vy][i]) {
                        q[++tail]=(data) {
                            vx,vy,i
                        };
                        b[vx][vy][i]=1;
                    }
                }
            }
        }
    }
    int ret=INF;
    for(int i=0; i^4; i++) {
        ret=min(ret,dis[tx][ty][i]);
    }
    return ret;
}
int main(int argc, char** argv) {
    cin>>n>>m>>l;
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            cin>>a[i][j];
        }
    }
    pre();
    for(int i=1; i<=l; i++) {
        cin>>ex>>ey>>sx>>sy>>tx>>ty;
        if(sx==tx&&sy==ty) {
            cout<<0<<endl;
            continue;
        }
        int ret=spfa();
        cout<<(ret<INF?ret:-1)<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值