1. 程式人生 > 實用技巧 >[ NOIP2013 D2-T3 ] 華容道

[ NOIP2013 D2-T3 ] 華容道

NOIP2013 華容道

圖論好題。 介於網上全是些令蒟蒻頭昏的題解和排版一塌糊塗以及過於詳細的題解。。。蒟蒻記錄一下。。

顯然需要將白格移動到 \(s\) 相鄰格,然後交換 \(s\) 與白格,再將白格移動到 \(s\) 相鄰格,然後交換 \(s\) 與白格……最後一步將白格移動到 與 \(s\) 相鄰的 \(t\) ,交換白格與 \(s\)

我們考慮到,將格子 \(a\)\((i,j)\) 移動到相鄰的格子 \(b\),需要讓白格先移動到 \(b\),之後交換 \(a\) 與白格。顯然 \(a\) 下一步還是要繼續往其他方向移動的,所以白格仍然需要移動到格子 \(a\) 的某個相鄰格。

我們寫了一個 bfs ,可以在白格在 \((ei,ej)\), s \((si,sj)\) 的時候處理一些值,接下來詳細闡述。


現在所闡述的 bfs 是在程式碼中 \(k=0,1,2,3\) 時的 bfs ,用於預處理建圖。

設定狀態 \((i,j,k)\) 表示 格子 \(s\)\((i,j)\) ,白格在 \((i,j)\) 的方向 \(k\) 處。( \(k\) : left right up down,用0,1,2,3表示)。

可以通過 bfs 計算出 狀態 \((i,j,k)\) 時, 白格開始移動,移動到棋盤上任意一個格子 \((x,y)\) 的花費 \(dis_{x,y}\)

。注意,在程式碼中,為了方便標記哪些是不可能移動到的點,將所有 \(dis_{x,y}\) 都加一了,但在題解中 \(dis_{x,y}\) 仍然是原先的值。

從狀態 \((i,j,k)\) 轉移到另一個狀態,有兩種情況。

  • 白格與 \(s\) 交換,在狀態 \((si,sj,k)\)\((ei,ej,k\and 1)\) 之間連邊 1。
  • 白格移動到了一個與 \(s\) 相鄰的位置 \((xx,yy)\) ,在狀態 \((si,sj,k)\) 與狀態 \((si,sj,i)\) 之間連邊 \(dis_{xx,yy}\).

如果要計算狀態 \((x1,y1,k1)\) 到狀態 \((x2,y2,k2)\)

之間的最小花費,只需要跑最短路即可。

也就是說,我們把狀態作為點,花費作為邊建圖。


現在闡述的是在程式碼中 \(k=4\) 時的 bfs,這是處理 白格到 初始格的相鄰格 花費的 bfs。

仍然先處理出所有的 \(dis_{x,y}\) , 意義與求法同 \(k=0,1,2,3\) 時一樣。將與 \(s\) 相鄰格的 \(dis_{x,y}\) 賦值在 dist 上。

最後計算答案,只需要 spfa 計算 \((tx,ty,0/1/2/3)\)\((sx,sy,0/1/2/3)\) 的最短路。

#include<bits/stdc++.h>
using namespace std;
const int N=35,M=4010;
int n,m,q,mp[N][N],dis[N][N],dist[M];
bool vis[M];
int e,to[M*5],nxt[M*5],val[M*5],hd[M];
int dx[5]={-1,1,0,0};
int dy[5]={0,0,-1,1};
void add(int x,int y,int w){ to[++e]=y; nxt[e]=hd[x]; val[e]=w; hd[x]=e; }
void bfs(int ei,int ej,int si,int sj,int dir){
    memset(dis,0,sizeof(dis));
    queue<pair<int,int> >q;
    q.push(make_pair(ei,ej)); dis[ei][ej]=1;
    while(!q.empty()){
        pair<int,int>w=q.front(); q.pop();
        int x=w.first,y=w.second;
        for(int i=0;i<4;i++){
            int xx=x+dx[i],yy=y+dy[i];
            if(!mp[xx][yy]||dis[xx][yy]||xx==si&&yy==sj) continue;
            dis[xx][yy]=dis[x][y]+1; q.push(make_pair(xx,yy));
        }
    }//白格向外瘋狂移動處理步數
    if(dir==4) return;
    
    for(int i=0;i<4;i++){
        int xx=si+dx[i],yy=sj+dy[i];
        if((xx==ei&&yy==ej)||!dis[xx][yy]) continue;
        add(si*30*4+sj*4+dir,si*30*4+sj*4+i,dis[xx][yy]-1);//白格移動
    }
    add(si*30*4+sj*4+dir,ei*30*4+ej*4+(dir^1),1);//交換
    return;
}
void spfa(int x,int y){
    memset(dist,0x3f,sizeof(dist));
    memset(vis,0,sizeof(vis));
   
    queue<int>q;
    for(int i=0;i<4;i++){
        if(!dis[x+dx[i]][y+dy[i]]) continue;
        int id=x*30*4+y*4+i; q.push(id);
		dist[id]=dis[x+dx[i]][y+dy[i]]-1; vis[id]=1;
    }
    while(!q.empty()){
        int id=q.front(); q.pop(); vis[id]=0;
      //  cout<<id<<endl;
        for(int i=hd[id];i;i=nxt[i]){
            int nxid=to[i],nxval=val[i];
        //    cout<<"nxid: "<<nxid<<endl;
            if(dist[nxid]>dist[id]+nxval){
                dist[nxid]=dist[id]+nxval;
                if(vis[nxid]) continue;
                vis[nxid]=1; q.push(nxid);
            }
        }
    }
    return;
}
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",&mp[i][j]);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(!mp[i][j]) continue;
            if(mp[i-1][j]) bfs(i-1,j,i,j,0);
            if(mp[i+1][j]) bfs(i+1,j,i,j,1);
            if(mp[i][j-1]) bfs(i,j-1,i,j,2);
            if(mp[i][j+1]) bfs(i,j+1,i,j,3);
        }
    }
    //預處理,建圖
    while(q--){
        int ex,ey,sx,sy,tx,ty;
    	scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
        if(sx==tx&&sy==ty){ puts("0"); continue; } 
        
        bfs(ex,ey,sx,sy,4); spfa(sx,sy); int ans=0x3f3f3f3f;
        for(int i=0;i<4;i++) ans=min(ans,dist[tx*30*4+ty*4+i]);
        (ans==0x3f3f3f3f)?puts("-1"):printf("%d\n",ans);
    }
    return 0;
}