洛谷P1979 華容道 題解
阿新 • • 發佈:2018-11-04
嚶嚶嚶泥古管理至今沒給申題解
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;
}