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
*/ 
京公网安备 11010502036488号