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,结束

思路

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
*/