NOIP 2013 华容道
题目描述
小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次。于是,他想到用编程来完成华容道:给定一种局面,华容道是否根本就无法完成,如果能完成,最少需要多少时间。
小 B 玩的华容道与经典的华容道游戏略有不同,游戏规则是这样的:
-
在一个 n*m 棋盘上有 n*m 个格子,其中有且只有一个格子是空白的,其余 n*m-1个格子上每个格子上有一个棋子,每个棋子的大小都是 1*1 的;
-
有些棋子是固定的,有些棋子则是可以移动的;
-
任何与空白的格子相邻(有公共的边)的格子上的棋子都可以移动到空白格子上。 游戏的目的是把某个指定位置可以活动的棋子移动到目标位置。
给定一个棋盘,游戏可以玩 q 次,当然,每次棋盘上固定的格子是不会变的,但是棋盘上空白的格子的初始位置、指定的可移动的棋子的初始位置和目标位置却可能不同。第 i 次玩的时候,空白的格子在第 行第
列,指定的可移动棋子的初始位置为第
行第
列,目标位置为第
行第
列。
假设小 B 每秒钟能进行一次移动棋子的操作,而其他操作的时间都可以忽略不计。请你告诉小 B 每一次游戏所需要的最少时间,或者告诉他不可能完成游戏。
【数据范围】
对于30% 30\%30%的数据,1≤n,m≤10,q=11 ≤ n, m ≤ 10,q = 11≤n,m≤10,q=1;
对于 60%60\%60%的数据,1≤n,m≤30,q≤101 ≤ n, m ≤ 30,q ≤ 101≤n,m≤30,q≤10;
对于 100%100\%100%的数据,1≤n,m≤30,q≤5001 ≤ n, m ≤ 30,q ≤ 5001≤n,m≤30,q≤500。
题解:
这个题,竟然闲的无事打了一个上午的暴力?!?!?!
可以很容易地想到,指定的棋子想要移动,必须把空格移到它的旁边。
所以,对于每组数据,我们必须先要bfs找到空格到棋子的四个方向的距离。
以这四个初始状态开始,找到到终点的最短距离。
基本上是循环这个操作:把空格子移到棋子的某个位置,(不能经过棋子自己),把棋子挪到空格上。
发现,每次空格移到棋子某个位置可以bfs,O(nm)
棋子的所有移动位置也是O(nm)
用一个spfa或者dij,就可以处理最短路了。
但是,复杂度就很高了,O(qn^2m^2log(nm))
发现,缺点在于,我们每次空格移到棋子的另一个位置,都要bfs一遍,实际上同一种情况会算重很多次。
所以,我们预处理啊!(多组询问经典套路处理方法)
设tt[x][y][d1][d2]表示,从(x,y)的d1方向的空格,不经过(x,y)到棋子的d2方向的空格,所花费的最短路。
这个枚举可以预处理。O(8n^2m^2)
然后,在每一个询问里面,空格互相移动的时候,直接查表好啦!!
复杂度:O(qnmlog(nm))(dijkstra最短路)
代码:
这里没有像一些代码建边,因为,其实边我们都是知道的。
而且,边的建造有一定规律,可以直接手动枚举出来,不需要再花费时空建边了。
尤其是在差分约束的时候,相邻的边直接枚举就好了,省了很多功夫。
#include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; const int N=32; int n,m; int mp[N][N]; int tt[N][N][4][4]; int mv[4][2]={{+1,0},{0,+1},{0,-1},{-1,0}}; int dis[N][N][4]; bool vis[N][N]; bool go[N][N][4]; int ex,ey,sx,sy,tx,ty; struct duilie{ int x,y,has; }que[N*N]; int l,r; int bfs1(int gx,int gy){//from ex,ey to gx,gy not pass sx,sy memset(vis,0,sizeof vis); l=1,r=0; que[++r].x=ex,que[r].y=ey; que[r].has=0; vis[ex][ey]=1; vis[sx][sy]=1; while(l<=r){ duilie now=que[l++]; if(now.x==gx&&now.y==gy) return now.has; for(int i=0;i<4;i++){ int dx=now.x+mv[i][0]; int dy=now.y+mv[i][1]; if(dx<1||dx>n) continue; if(dy<1||dy>m) continue; if(vis[dx][dy]) continue; if(!mp[dx][dy]) continue; vis[dx][dy]=1; que[++r].x=dx,que[r].y=dy; que[r].has=now.has+1; } } return -1; } int bfs2(int x,int y,int gx,int gy,int nx,int ny){// from (x,y) to (gx,gy) not pass (nx,ny); memset(vis,0,sizeof vis); vis[x][y]=1; vis[nx][ny]=1; l=1;r=0; que[++r].x=x,que[r].y=y; que[r].has=0; while(l<=r){ duilie now=que[l++]; if(now.x==gx&&now.y==gy) return now.has; for(int i=0;i<4;i++){ int dx=now.x+mv[i][0]; int dy=now.y+mv[i][1]; if(dx<1||dx>n) continue; if(dy<1||dy>m) continue; if(vis[dx][dy]) continue; if(!mp[dx][dy]) continue; vis[dx][dy]=1; que[++r].x=dx,que[r].y=dy; que[r].has=now.has+1; } } return -1; } struct node{ int x,y,d; int sum; bool friend operator <(node a,node b){ return a.sum>b.sum; } }; priority_queue<node>q; int wrk(){ if(!mp[sx][sy]) return -1; if(!mp[tx][ty]) return -1; if(!mp[ex][ey]) return -1; if(sx==tx&&sy==ty) return 0; memset(dis,inf,sizeof dis); for(int i=0;i<4;i++){ int dx=sx+mv[i][0],dy=sy+mv[i][1]; if(!mp[dx][dy]) continue; if(dx<1||dx>n) continue; if(dy<1||dy>m) continue; int dist=bfs1(dx,dy); if(dist!=-1){ dis[sx][sy][i]=dist; node lp; lp.x=sx,lp.y=sy,lp.d=i; lp.sum=dist; q.push(lp); } } memset(go,0,sizeof go); while(!q.empty()){ node now=q.top();q.pop(); if(go[now.x][now.y][now.d]) continue; go[now.x][now.y][now.d]=1; dis[now.x][now.y][now.d]=now.sum; for(int i=0;i<4;i++){ if(i==now.d) continue; int dx=now.x+mv[i][0],dy=now.y+mv[i][1]; if(!mp[dx][dy]) continue; if(dx<1||dx>n) continue; if(dy<1||dy>m) continue; int dist=tt[now.x][now.y][now.d][i]; if(dist!=-1){ if(!go[now.x][now.y][i]){ node lp; lp.x=now.x,lp.y=now.y; lp.d=i; lp.sum=now.sum+dist; q.push(lp); } } } int dx=now.x+mv[now.d][0],dy=now.y+mv[now.d][1]; if(!go[dx][dy][3-now.d]){ node lp; lp.x=dx,lp.y=dy; lp.d=3-now.d; lp.sum=now.sum+1; q.push(lp); } } int ret=inf; for(int i=0;i<4;i++){ ret=min(ret,dis[tx][ty][i]); } if(ret==inf) return -1; else return ret; } void clear(){ while(!q.empty()) q.pop(); } int T; int main() { scanf("%d%d%d",&n,&m,&T); 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++){//pre work for(int j=1;j<=m;j++){ if(!mp[i][j]) continue; for(int k=0;k<4;k++){ int kx=i+mv[k][0],ky=j+mv[k][1]; if(!mp[kx][ky]) continue; if(kx<1||kx>n) continue; if(ky<1||ky>m) continue; for(int p=k+1;p<4;p++){ int px=i+mv[p][0],py=j+mv[p][1]; if(!mp[i][j]) continue; if(px<1||px>n) continue; if(py<1||py>m) continue; int dist=bfs2(kx,ky,px,py,i,j); tt[i][j][k][p]=tt[i][j][p][k]=dist; } } } } while(T--){ scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty); clear(); printf("%d\n",wrk()); } return 0; }
总结:
其实思路挺简单的。暴力处理比较好想,然后优化也比较自然就出来了。
但是就是没想到??
还有,不能直接bfs最短路,因为边权不一定是1啊!!
以后对多组询问预处理还是要注意,
经常会有比较耗时间的地方,却还要算多次。
(相比较于搜索,dp时间的优化,本质上就是重复状态的一次计算——ZRT)