題目鏈接
說在前面: 首先這道題比較冗雜,如果因爲您不太接受我的碼風/表達方式而帶來了不太好的閱讀效果,我向您道歉(鞠躬
題面請戳鏈接,請您在確保對這道題目的背景和做這道題的大致方向有了一定的掌握後,再食用這篇博客,以確保較好的閱讀效果
除去多次詢問不談,這道題大致的框架是非常明顯的搜索。但是雖然是多次詢問,棋盤的狀態是不會變的,並且只有部分的位置是真正有價值的。那麼就可以考慮圖論建模,在這些有價值的狀態上跑從終態到末態的最短路。因爲棋子要移動的時候,空白塊一定要移到它的周圍,所以我們可以爲這種周圍的位置編號。表示在點的方向上的位置的編號。這裏我們有兩個數組表示方向的偏移量,不同的分別配對左、右、上、下:。這樣編號的好處是,即可表示相反的方向
點建好了,接下來考慮怎麼連邊。顯然的一點是,爲了使目標棋子更快地前進,空白格移動到棋子旁的路上不應該與棋子發生交換。對於一個在棋子旁邊的空白格,它有以下的選擇:
- 繞到棋子的另一個方向上,使得棋子向該方向前進,即:$f[x][y][i]\to f[x][y][j](i\not= j) $
- 直接和棋子交換位置,讓棋子向原本的空白格的方向前進,即:
由於第一種選擇不能受到各種元素限制,所以需要bfs來確定邊權。作完這些預處理之後,這些點之間就構成了一個圖論模型。對於每次詢問,對移動的棋子周圍的4個位置做一次bfs,得到讓空白格(在不移動該棋子的情況下)移動到棋子四周的最小代價。再把這四個初狀態壓入隊列中,跑一遍,最後枚舉末狀態取一個的就是答案了
還有一些細節:
- 起點和終點相同時記得特判
- 正確估計模型中最多會有多少個點來開數組
具體請結合代碼理解:
//從main開始看
#include <cstdio>
#include <cstring>
const int maxn=50;
const int maxm=1000000;
const int INF=0x3f3f3f3f;
int a[maxn][maxn],f[maxn][maxn][4];
int dx[4]={0, 0, 1, -1},dy[4]={1, -1, 0, 0};
int head[maxn*maxn*4],to[maxm],nxt[maxm],val[maxm];
int dis[maxn*maxn];
int cnt,tot;
int n,m,Q;
bool vis[maxn][maxn],inq[maxn*maxn];
struct node
{
int x,y,w;
node() {}
node(int a,int b,int c) {x=a,y=b,w=c;}
};
struct Queue
{
int l,r;
node a[maxn*maxn];
Queue() {l=1,r=0;}
void push(node x) {a[++r]=x;}
void pop() {l++;}
node front() {return a[l];}
bool empty() {return l>r;}
};//這兩個手寫隊列分別給bfs和SPFA用= =,直接用STL也差不多的
struct Que
{
int l,r;
int a[maxn*maxn];
Que() {l=1,r=0;}
void push(int x) {a[++r]=x;}
void pop() {l++;}
int front() {return a[l];}
bool empty() {return l>r;}
};
int min(int x,int y) {return x<y?x:y;}
bool valid(int x,int y) {return a[x][y];}
void add(int u,int v,int w)
{
nxt[++tot]=head[u];
head[u]=tot;
to[tot]=v;
val[tot]=w;
}
int bfs(int target_x,int target_y,int sx,int sy,int tx,int ty)
{
if (sx==tx&&sy==ty)
return 0;
memset(vis, 0, sizeof(vis));
Queue q;
vis[sx][sy]=1;
vis[target_x][target_y]=1;//特殊標記一下,就當作走過了,反正不能走
q.push(node(sx, sy, 0));
while(!q.empty())
{
int ux=q.front().x,uy=q.front().y,uw=q.front().w;
q.pop();
for (int i=0;i<4;i++)
{
int vx=ux+dx[i],vy=uy+dy[i];
if (!valid(vx, vy)||vis[vx][vy])
continue;
if (vx==tx&&vy==ty)
return uw+1;
vis[vx][vy]=1;
q.push(node(vx, vy, uw+1));
}
}
return INF;
}
int SPFA(int ex,int ey,int sx,int sy,int tx,int ty)
{
if (sx==tx&&sy==ty)//特判一下
return 0;
memset(dis, 0x3f, sizeof(dis));
memset(inq, 0, sizeof(inq));
Que q;
for (int i=0;i<4;i++)
{
int vx=sx+dx[i],vy=sy+dy[i];
if (!valid(vx, vy))
continue;
dis[f[sx][sy][i]]=bfs(sx, sy, ex, ey, vx, vy);
q.push(f[sx][sy][i]);
inq[f[sx][sy][i]]=1;
}
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=0;
for (int i=head[u];i;i=nxt[i])
{
int v=to[i];
if (dis[v]>dis[u]+val[i])
{
dis[v]=dis[u]+val[i];
if (!inq[v])
inq[v]=1,q.push(v);
}
}
}
int ans=INF;
for (int i=0;i<4;i++)
ans=min(ans, dis[f[tx][ty][i]]);
return ans==INF?-1:ans;
}
int main()
{
scanf("%d%d%d",&n,&m,&Q);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
for (int x=1;x<=n;x++)
for (int y=1;y<=m;y++)
for (int i=0;i<4;i++)
{
int vx=x+dx[i],vy=y+dy[i];
if (!valid(vx, vy))
continue;
f[x][y][i]=++cnt;//給有用狀態編號
}
for (int x=1;x<=n;x++)
for (int y=1;y<=m;y++)
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
{
if (i==j||!valid(x+dx[i], y+dy[i])||!valid(x+dx[j], y+dy[j]))
continue;
add(f[x][y][i], f[x][y][j], bfs(x, y, x+dx[i], y+dy[i], x+dx[j], y+dy[j]));
}//選擇1
for (int x=1;x<=n;x++)
for (int y=1;y<=m;y++)
for (int i=0;i<4;i++)
{
if (!valid(x+dx[i], y+dy[i]))
continue;
add(f[x][y][i], f[x+dx[i]][y+dy[i]][i^1], 1);//選擇2,交換空白格與棋子的位置,將這兩個狀態連邊
}
while(Q--)
{
int ex,ey,sx,sy,tx,ty;
scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
printf("%d\n",SPFA(ex, ey, sx, sy, tx, ty));
}
return 0;
}