1. 程式人生 > >洛谷P1979 華容道 題解

洛谷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