先扔个代码了……之后再写详细点吧
感谢@Erutsiom 巨佬的解读题目
dalao题解链接
//by Saber Alter Official //Erishikigal & Ishtar #include <cstdio> #include <iostream> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> //#define Kama typedef long long ll; typedef unsigned long long ull; const int N = 35, inf = 0x3f3f3f3f; int n, m, q, ex, ey, sx, sy, tx, ty; bool G[N][N], OK[N][N][5];//OK[i][j][k]表示绿色格子在(i,j)位置的k方向是否能有空白格子 int mv[4][2] = {{-1, 0},{1, 0},{0, -1},{0, 1}};//0上 1下 2左 3右 /*建边操作*/ struct node { int to, next, val; }edge[5015]; int head[5015], cnt; inline void add_edge(int u, int v, int val) { edge[++cnt].to = v; edge[cnt].next = head[u]; edge[cnt].val = val; head[u] = cnt; } /*建边结束*/ /*合法性检验*/ inline bool Check(int x, int y) { if(x <= 0 || y <= 0 || x > n || y > m || !G[x][y]) return false; return true; } /*编号函数第一次看比较难理解 举个例子,我们让(1,1)格子四个状态为0123 (1,2)格子四个状态是4567, 以此类推 就可以得到一个横行里相同状态比上一列多4。 再假设一共五列,(2,1)就是20.21.22.23,比上一行相同列相同状态多了4*m个 所以可以推出x上的运算应该是要乘以m,为了让第一行出现0123,要先把x变成(x-1) 然后加上y,最外面乘4,就可以得到上面的倍数关系,最后加上dir-4就可以了 */ inline int SetNum(int x, int y, int dir) { return (((x - 1) * m + y) * 4 + (dir - 4)); } struct Vacancy { int x, y, step; }; std::queue<Vacancy> Que; inline void Clear(std::queue<Vacancy> &Que) { std::queue<Vacancy> Empty; std::swap(Que, Empty); } //不能通过fbdx,fbdy从stx,sty到tarx, tary inline int BFS(int fbdx, int fbdy, int stx, int sty, int tarx, int tary) { //初始化 Clear(Que); bool vis[N][N]; memset(vis, 0, sizeof vis); //初始化队头 Vacancy head; head.x = stx; head.y = sty; head.step = 0;//起步是0哦不要打成1 Que.push(head); while(!Que.empty()) { Vacancy pos = Que.front(); Que.pop(); if(pos.x == tarx && pos.y == tary) { return pos.step; } if(vis[pos.x][pos.y]) continue; vis[pos.x][pos.y] = true; for(int k = 0;k < 4;k++) { Vacancy New; New.x = pos.x + mv[k][0]; New.y = pos.y + mv[k][1]; if(Check(New.x, New.y)) {//新位置本身合法 if(vis[New.x][New.y]) continue;//不符合两种条件 if(New.x == fbdx && New.y == fbdy) continue; New.step = pos.step + 1; Que.push(New); } } } return inf;//队列跑空了也找不到 } void init() { scanf("%d %d %d", &n, &m, &q); for(int i = 1;i <= n;i++) { for(int j = 1;j <= m;j++) { scanf("%d", &G[i][j]); } } #ifdef Kama for(int i = 1;i <= n;i++) { for(int j = 1;j <= m;j++) { printf("%d%c", G[i][j], j == m ? '\n' : ' '); } } #endif for(int i = 1;i <= n;i++) { for(int j = 1;j <= m;j++) { if(!G[i][j]) continue;//本身就不能走动 for(int k = 0;k < 4;k++) { if(Check(i + mv[k][0], j + mv[k][1]))//绿格子和空白格子的移动处理 OK[i][j][k] = true;//预处理移动,看这个位置向哪里移动合法 } } } //空格绕着绿色格子乱动的距离 for(int i = 1;i <= n;i++) { for(int j = 1;j <= m;j++) {//i,j枚举绿色格子位置 for(int k = 0;k < 4;k++) { for(int p = k + 1;p < 4;p++) {//空白格在各个位置尝试移动 if(OK[i][j][k] && OK[i][j][p]) {//k=1 p=2.3.4 //k=2 p=3.4//k=3 p=4就枚举了所有情况 int _knum = SetNum(i, j, k);//给格子编号,用来存在图里 int _pnum = SetNum(i, j, p);//一样啦 int _step = BFS(i, j, i + mv[k][0], j + mv[k][1], i + mv[p][0], j + mv[p][1]); if(_step == inf) continue;//不可能到达 add_edge(_knum, _pnum, _step);//建边! add_edge(_pnum, _knum, _step); } } } } } //空格和绿色格子打包移动,也就是不断交换位置来移动 //这是左右来回推进 for(int i = 1;i <= n;i++) { for(int j = 1;j < m;j++) { if(OK[i][j][3] && OK[i][j + 1][2]) {//j在左边,右有空格 int _lefNum = SetNum(i, j + 1, 2);//j+1在右边,左有空格 int _rigNum = SetNum(i, j, 3); add_edge(_lefNum, _rigNum, 1); add_edge(_rigNum, _lefNum, 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 _upNum = SetNum(i, j, 1); int _dowNum = SetNum(i + 1, j, 0); add_edge(_upNum, _dowNum, 1); add_edge(_dowNum, _upNum, 1); } } } } int dis[5005];//dis开小了!!!!!!!!!!!!!!!!!!!!!!!!! std::queue<int> que; bool vis[5005]; inline void Solve() { memset(dis, 0x3f3f3f3f, sizeof dis); memset(vis, 0, sizeof vis); #ifdef Kama for(int i = 1;i <= 100;i++) { printf("%d\n", dis[i]); } #endif for(int k = 0;k < 4;k++) { if(Check(sx + mv[k][0], sy + mv[k][1])) {//枚举空白格到绿色格子周围的情况 int nowdis = BFS(sx, sy, ex, ey, sx + mv[k][0], sy + mv[k][1]); if(nowdis == inf) continue; int nownum = SetNum(sx, sy, k); que.push(nownum); dis[nownum] = nowdis; #ifdef Kama printf("dis:%d\n", dis[nownum]); #endif vis[nownum] = true; } } //上面的循环是针对每次询问,先让空白格跑到绿色格子周围,完成一个初始化 //接下来,因为我们已经得到的绿色和空白格子在一起的位置编号 //又已经建好了这个棋盘上所有绿色和空白格子一起互达的图,就可以跑spfa了! while(!que.empty()) { int u = que.front(); que.pop(); vis[u] = false; for(int i = head[u];i;i = edge[i].next) { int v = edge[i].to; if(dis[v] > dis[u] + edge[i].val) { dis[v] = dis[u] + edge[i].val; #ifdef Kama printf("cdis:%d %d\n", dis[v], dis[u]); #endif if(!vis[v]) { #ifdef Kama printf("v:%d\n", v); #endif que.push(v); vis[v] = true; } } } } /*最后检查一下答案就可以了*/ int ans = inf; for(int k = 0;k < 4;k++) { int tarnum = SetNum(tx, ty, k); ans = std::min(ans, dis[tarnum]); #ifdef Kama printf("tar:%d\n", tarnum); printf("dis:%d%c", dis[tarnum], k == 3 ? '\n' : ' '); #endif } if(ans == inf) printf("-1\n"); else { printf("%d\n", ans); } } void Query() { for(int i = 1;i <= q;i++) {//空白格信息 绿色格子信息 终点信息 scanf("%d %d %d %d %d %d", &ex, &ey, &sx, &sy, &tx, &ty); if(sx == tx && sy == ty) {//本身在终点 printf("0\n"); continue; } else if(!G[sx][sy]) {//起点不合法 printf("-1\n"); continue; } else if(!G[tx][ty]) {//终点不合法 printf("-1\n"); continue; } Solve(); } } int main() { init(); Query(); return 0; }