11166E-Escape along Water Pipe
补E题,搞了大半天才理解做法,翻了好多提交都是没注释的,也没人写题解,蒟蒻看得很是辛苦,所以写篇带注释的代码,希望能帮到一些人吧
题意
从沿着管道逃脱
输入:T样例数,n、m图的大小,n行m列表示每个位置的初始状态(1~6)
输出:YESorNO
如果是YES,输出k,表示操作数
接下来k行:
t d:表示有t个格子的水管要旋转d度
接下来2t个数,表示t个格子坐标
0 x y:表示要前往(x,y)的格子
如果是NO,结束
输入:T样例数,n、m图的大小,n行m列表示每个位置的初始状态(1~6)
输出:YESorNO
如果是YES,输出k,表示操作数
接下来k行:
t d:表示有t个格子的水管要旋转d度
接下来2t个数,表示t个格子坐标
0 x y:表示要前往(x,y)的格子
如果是NO,结束
思路
bfs模拟一遍从起点到终点,同时记录来的反方向,如果能到终点,再通过记录的反方向dfs跑回起点,同时记录回去的反方向,然后翻转一下回去记录的反方向数组,就是从起点到终点的正方向数组啦。
最后从起点到终点跑一遍正方向数组,再根据每次位移旋转格子即可,题目的限制貌似没啥用,能到就是能到,不能就是不能。
具体看代码的注释吧,感觉写得挺清楚了。
代码
#include <bits/stdc++.h> #define INF 99999999 #define LINF LLONG_MAX using namespace std; /* #2021多校Round1 E题:沿着管道逃脱 输入:T样例数,n、m图的大小,n行m列表示每个位置的初始状态(1~6) 输出:YESorNO 如果是YES,输出k,表示操作数 接下来k行: t d:表示有t个格子的水管要旋转d度 接下来2t个数,表示t个格子坐标 0 x y:表示要前往(x,y)的格子 如果是NO,结束 */ typedef long long ll; typedef unsigned long long ull; const int MAX_N=1010; int T,n,m; int M[MAX_N][MAX_N]; int vis[MAX_N][MAX_N][5]; struct node{ //坐标,返回的方向 int x,y,d; }; queue<node> q; vector<int> opt; //位移数组:上 左 右 下 int dx[5]={0,-1,0,0,1}; int dy[5]={0,0,-1,1,0}; inline void bfs(){ //清空队列 while(!q.empty()) q.pop(); //清空操作 opt.clear(); //起点入队,起点的d是1,因为是从(0,1)->(1,1),返回要选择dx[1]&dy[1] q.push((node){1,1,1}); vis[1][1][1]=1; //bfs while(!q.empty()){ //取出队首元素 int x=q.front().x,y=q.front().y,d=q.front().d; //队首出队 q.pop(); /*在终点时: 如果是0~3类型的管道,返回要选择dx[2]&dy[2] 如果是4~5类型的管道,返回要选择dx[1]&dy[1] 如果满足,则bfs结束 */ if(x==n&&y==m&&((M[x][y]<=3&&d==2)||(M[x][y]>3&&d==1))) break; //不在终点时:如果是0~3类型的管道 if(M[x][y]<=3){ //遍历四个方向 for(int i=1;i<=4;i++){ /*违规情况: i==d表示来的方向,肯定是不能走的 i+d==5表示直线方向,0~3类型的管道也不能走直线 */ if(i==d||i+d==5) continue; //位移 int nx=x+dx[i],ny=y+dy[i]; //越界跳过 //已访问跳过 if((nx>=1&&nx<=n&&ny>=1&&ny<=m)&&vis[nx][ny][5-i]==-1){ //记录来的反方向为已访问 vis[nx][ny][5-i]=d; //当前节点入队 q.push((node){nx,ny,5-i}); } } } //如果是4~5类型的管道 else{ //当然只能走直线啦 int nx=x+dx[5-d],ny=y+dy[5-d]; //越界跳过 //已访问跳过 if((nx>=1&&nx<=n&&ny>=1&&ny<=m)&&vis[nx][ny][d]==-1){ //记录来的反方向为已访问 vis[nx][ny][d]=d; //当前节点入队 q.push((node){nx,ny,d}); } } } return; } int main(){ ios_base::sync_with_stdio(false); cout.tie(0); cin.tie(0); //freopen("1.out","w",stdout); cin>>T; while(T--){ //输入 cin>>n>>m; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ cin>>M[i][j]; //初始化vis数组 memset(vis[i][j],-1,sizeof(vis[i][j])); } //宽搜 bfs(); /*判断是否能从(0,1)到(n+1,m): 如果是0~3类型的管道,返回要选择dx[2]&dy[2],所以vis[n][m][2]需为已访问 如果是4~5类型的管道,返回要选择dx[1]&dy[1],所以vis[n][m][1]需为已访问 */ if((M[n][m]<=3&&vis[n][m][2]!=-1)||(M[n][m]>3&&vis[n][m][1]!=-1)){ //可以到终点输出YES cout<<"YES\n"; /*从(n,m)到(1,1): 如果M[n][m]<=3则是0~3类型的管道,方向为2; 否则是4~5类型的管道,方向为1。 */ int x=n,y=m,d=M[n][m]<=3?2:1; //先从(n+1,m)到(n,m)是上方向,所以记录位移数组中表示下的4 opt.push_back(4); //深搜回起点 while(x!=1||y!=1){ //记录操作的方向 opt.push_back(5-d); int w=d; d=vis[x][y][d]; x+=dx[w]; y+=dy[w]; } //输出操作数 cout<<2*opt.size()<<'\n'; //最后从(1,1)到(0,1)是上方向,所以记录位移数组中表示下的4 opt.push_back(4); //翻转,因为我们要从起点出发 reverse(opt.begin(),opt.end()); //再从起点到终点 x=0; y=1; //遍历所有操作 for(int i=1;i<opt.size();i++){ //位移 int nx=x+dx[opt[i-1]]; int ny=y+dy[opt[i-1]]; //转角度操作 cout<<"1 "; //如果是0~3类型的管道 if(M[nx][ny]<=3){ //判断管道类型 int dg; //如果差值为2,则仅可能是3->1或4->2,即右->上或下->左(类型0) if(opt[i-1]-opt[i]==2) dg=0; //如果差值为1,则仅可能是4->3或2->1,即下->右或左->上(类型1) else if(opt[i-1]-opt[i]==1) dg=1; //如果差值为-2,则仅可能是1->3或2->4,即上->右或左->下(类型2) else if(opt[i-1]-opt[i]==-2) dg=2; //如果差值为-1,则仅可能是3->4或1->2,即右->下或上->左(类型3) else if(opt[i-1]-opt[i]==-1) dg=3; //输出旋转角度=90*(管道编号差值) cout<<90*((dg-M[nx][ny]+4)%4)<<' '; //更新管道类型 M[nx][ny]=dg; } //如果是4~5类型的管道 else{ //要么转90度要么不转 int dg=0; //如果是类型4管道,当上->上或下->下时要转 //如果是类型5管道,当左->左或右->右时要转 if((M[nx][ny]==4&&(opt[i-1]*opt[i]==1||opt[i-1]*opt[i]==16))||(M[nx][ny]==5&&(opt[i-1]*opt[i]==4||opt[i-1]*opt[i]==9))){ dg=90; //4变5,5变4 M[nx][ny]^=1; } //输出旋转角度 cout<<dg<<' '; } //输出要旋转的格子 cout<<nx<<' '<<ny<<'\n'; //输出位移的格子 cout<<"0 "<<nx<<' '<<ny<<'\n'; x=nx; y=ny; } } else{ //不可到终点输出NO cout<<"NO\n"; } } return 0; } /* 样例 2 2 2 4 5 0 1 2 2 0 1 2 3 输出 YES 5 2 90 1 1 2 1 0 1 1 0 2 1 2 180 1 2 2 2 0 2 2 NO */