[NOIP2013 提高组] 华容道 题解
[NOIP2013 提高组] 华容道
题意:
一个棋盘上,每个格子上都有一个
思路:
这类有少量空白和大量棋子的问题,一个很重要的想法就是把棋子的移动转化成空白的移动。然后发现,局面只和空白格的位置与目标棋子的位置有关系,那就可以 bfs 了,让空白格子随意动,目标棋子第一次到达终点的时间就是最短时间。这样有 70pts。
然后有个很重要的性质,就是局面的改变只会发生在空白格子与目标棋子相邻的时候,且一次改变后目标棋子只会向四个方向移动。如果我们把每个状态(不同的状态包括目标棋子与空白格子相对位置不同)都抽象成结点,然后把状态的改变看做边,花费的步数看作边权,则就可以用最短路求解了。求边权的过程用 bfs 即可。
代码:
#include<bits/stdc++.h> namespace IO{ const int sz = 1<<22; char a[sz+5], b[sz+5], *p1 = a, *p2 = a, *t = b, p[105]; char gc(){return p1 == p2?(p2 = (p1 = a)+fread(a, 1, sz, stdin), p1 == p2?EOF:*p1++):*p1++;} template<class T> void gi(T &x){ x = 0; char ch = gc(); while(ch<'0' || ch>'9') ch = gc(); while(ch>='0'&&ch<='9') x = x*10+(ch-48), ch = gc(); } void flush(){fwrite(b, 1, t-b, stdout); t = b;} void pc(char ch){*t++ = ch; if(t-b==sz) flush();} template<class T> void pi(T x, char ch = '\n'){ if(x == 0) pc('0'); if(x < 0) pc('-'), x = -x; int tp = 0; while(x){ p[++tp] = x%10+48; x/=10; } while(tp){ pc(p[tp--]); } pc(ch); } struct F{~F(){flush();}}f; } using IO::gi; using IO::pi; //快读快写 using namespace std; const int INF = 0x3f3f3f3f; const int N = 35; const int NN = 4000, MM = 120000; struct node{ int nxt, to, w; }edge[MM<<1]; int head[NN], tot; void add(int u, int v, int w){ edge[++tot].nxt = head[u]; edge[tot].to = v; edge[tot].w = w; head[u] = tot; } struct point{ int x, y; bool operator == (const point &b) const{ return x == b.x && y == b.y; } }; // point targ; bool vis[N][N][N][N];//目标棋子x, y;空白格x, y。 int dx[4] = {0, 1, 0, -1};//0 1 2 3: 左上右下 int dy[4] = {-1, 0, 1, 0}; int mp[N][N]; int id[N][N][4], idx; struct xwx{ int xw, yw, xt, yt;//白格,目标棋子。 int step; bool operator == (const xwx &b) const{ return xw == b.xw && yw == b.yw && xt == b.xt && yt == b.yt; } }; xwx targ, stk[1000000]; int top; int n, m, Q; int del; int bfs(xwx st){ if(st == targ) return 0; while(top){ vis[stk[top].xt][stk[top].yt][stk[top].xw][stk[top].yw] = 0; --top; } // memset(vis, 0, sizeof(vis)); queue<xwx> q; q.push(st); while(!q.empty()){ xwx tmp = q.front(); q.pop(); int tx, ty, nx = tmp.xt, ny = tmp.yt; for(int o = 0; o<4; ++o){ tx = tmp.xw+dx[o], ty = tmp.yw+dy[o]; if((!mp[tx][ty]) || tx < 1 || tx > n || ty < 1 || ty > m) continue; if(tx == nx && ty == ny){ if(vis[tmp.xw][tmp.yw][tx][ty]) continue; vis[tmp.xw][tmp.yw][tx][ty] = 1; xwx nxt = (xwx){tx, ty, tmp.xw, tmp.yw, tmp.step+1}; stk[++top] = nxt; if(nxt == targ) return tmp.step+1; q.push(nxt); } else{ if(vis[nx][ny][tx][ty]) continue; vis[nx][ny][tx][ty] = 1; xwx nxt = (xwx){tx, ty, nx, ny, tmp.step+1}; stk[++top] = nxt; if(nxt == targ) return tmp.step+1; q.push(nxt); } } } return INF; } int dst[NN]; struct uwu{ int p; int dst; bool operator <(const uwu &b) const{ return dst > b.dst; } }; priority_queue<uwu> q; bool vs[NN]; void dij(int st){ memset(dst, 0x3f, sizeof(dst)); memset(vs, 0, sizeof(vs)); dst[st] = 0; q.push((uwu){st, dst[st]}); while(!q.empty()){ int u = q.top().p; q.pop(); if(vs[u]) continue; vs[u] = 1; for(int i = head[u]; i; i = edge[i].nxt){ int v = edge[i].to; if(!vs[v] && dst[u] + edge[i].w < dst[v]){ dst[v] = dst[u] + edge[i].w; q.push((uwu){v , dst[v]}); } } } } int main(){ gi(n), gi(m), gi(Q); del = n+m; for(int i = 1; i<=n; ++i){ for(int j = 1; j<=m; ++j){ gi(mp[i][j]); } } for(int i = 1; i<=n; ++i){ for(int j = 1; j<=m; ++j){ int tx, ty; for(int o = 0; o<4; ++o){ tx = i+dx[o], ty = j+dy[o]; if(tx<1 || tx>n || ty<1 || ty>m || (!mp[tx][ty])) continue; id[i][j][o] = ++idx; } } } for(int i = 1; i<=n; ++i){ for(int j = 1; j<=m; ++j){//枚举目标块在哪里 int tx, ty; for(int d = 1; d<3; ++d){ tx = i+dx[d], ty = j+dy[d];//枚举右和上方向相邻的方格。(转移到的下一步) if(tx<1 || tx>n || ty<1 || ty>m || (!mp[tx][ty])) continue; int stx, sty, ttx, tty;//枚举起点与终点空白格的位置。 for(int os = 0; os<4; ++os){ stx = i+dx[os], sty = j+dy[os]; if(stx<1 || stx>n || sty<1 || sty>m || (!mp[stx][sty])) continue; for(int ot = 0; ot<4; ++ot){ ttx = tx+dx[ot], tty = ty+dy[ot]; if(ttx<1 || ttx>n || tty<1 || tty>m || (!mp[ttx][tty])) continue; targ = (xwx){ttx, tty, tx, ty, 0}; int dst = bfs((xwx){stx, sty, i, j, 0}); if(dst!=INF){ add(id[i][j][os], id[tx][ty][ot], dst); add(id[tx][ty][ot], id[i][j][os], dst); } } } } } } int ex, ey, sx, sy, tx, ty; while(Q--){ gi(ex), gi(ey), gi(sx), gi(sy); gi(tx);gi(ty); if(sx == tx && sy == ty){//如果就在终点则空白格子不用动,所以要特判。 IO::pc('0'); IO::pc('\n'); continue; } int stx, sty, ans = INF, fst; for(int o = 0; o<4; ++o){ if(!id[sx][sy][o]) continue; stx = sx+dx[o], sty = sy+dy[o]; targ = (xwx){stx, sty, sx, sy}; fst = bfs((xwx){ex, ey, sx, sy});//注意要先移动空白到目标棋子的附近。 dij(id[sx][sy][o]); for(int ot = 0; ot<4; ++ot){ if(!id[tx][ty][ot]) continue; ans = min(ans, fst+dst[id[tx][ty][ot]]); } } if(ans == INF) ans = -1; pi(ans, '\n'); } return 0; }