输入样例:
5 6 2
3 0 /
0 2 /
1 2 /
3 2 \
1 3 \
输出样例:
4
样例解释
农场的平面图如下所示,其中
H 表示约翰的房子,
B 表示牛棚:
3 .\.....
2 //.\..B
1 .......
0 H../...
0123456
通过改变 (3,2)处的围栏朝向,就可以使约翰看到点 (a,b),如下所示:
3 .\.....
2 //./--B
1 ...|...
0 H--/...
0123456
3 .\.....
2 //./--B
1 ...|...
0 H--/...
0123456
题意解读:
可以理解为从原点(0,0)开始向右发射一束光,通过镜子/ \两种45°倾斜的镜子反射光,则光可以90°的角度变化到上下左右另一方向继续射出,最终需要射到终点(a,b)。
最多改变一个镜子能达到如上目标则输出改变镜子的编号,否则输出-1,不改变镜子则输出0.
思路:枚举
-
将不翻转时和每次翻转第i个镜子的路径遍历一遍看是否走到终点,需要n+1次
-
遍历路径方法:将镜子视为节点,从当前原点节点出发,每次当前节点都需要找到下一个镜子节点(即该镜子在当前方向上,且距离最近),方法为遍历所有的镜子节点,查看两个节点构成的向量的模乘上当前目标方向得到一个向量,再加当前节点,查看是否等于下一个节点。直到找到终点或者找不到终点。
-
每一个路径遍历时会出现三种情况:
- 1.如果走了(2n+1)个镜子节点了还未到终点,则一定是进入了循环到不了终点(注:2n+1是所有镜子形成循环即最大的循环需要的节点数)
-
2.如果在当前节点找不到下一个镜子节点,则说明此方向没有镜子了,一定到不了终点
-
3.到达终点
-
由上可知路径遍历需要n^2时间复杂度
-
所以枚举算法需要n^3的时间复杂度,因为n<200,故可以接受
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <vector>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 210;
int n;
// 定义方向,上 右 下 左
int dx[] = {0,1,0,-1};
int dy[] = {1,0,-1,0};
struct Node{
int x,y;
char mirror;
}M[N];
void change_mirror(Node &node){
// 翻转镜子
if(node.mirror == '\\') node.mirror = '/';
else node.mirror = '\\';
}
bool check(){
// 遍历当前的镜子节点路径是否能到达终点
int d = 1; // 方向向右
int index = 0; // 当前的镜子
M[0].x = M[0].y = 0; // 初始化开始位置为原点
for(int i = 0;i<(2*n+1);i++){
// 遍历最多(2*n+1),则一定是陷入循环了,不可能到达终点
int next = -1,len = INF; // 下一个节点和下一个节点到此节点的距离
for(int j = 1;j<=n+1;j++){
if(index == j) continue; // 相同节点直接跳过
// 当前节点 加 当前方向乘模长 需要等于 目标节点
if(M[index].x + abs(M[index].x - M[j].x) * dx[d] != M[j].x) continue;
if(M[index].y + abs(M[index].y - M[j].y) * dy[d] != M[j].y) continue;
// 找到最近的那个节点
int length = abs(M[index].x - M[j].x) + abs(M[index].y - M[j].y);
if(length < len) len = length, next = j;
}
if(next == -1) return false; // 找不到下一个节点
if(next == n+1) return true; // 找到了目标节点
// 根据下一个节点的镜子更换方向
index = next;
if(M[next].mirror == '/'){
if(d == 0) d = 1;
else if(d == 1) d = 0;
else if(d == 2) d = 3;
else d = 2;
}else{
if(d == 0) d = 3;
else if(d == 1) d = 2;
else if(d == 2) d = 1;
else d = 0;
}
}
return false;
}
int main(){
/* 思路:枚举
* 将每一个翻转i镜子的路径和所有都不翻转遍历一遍看是否走到终点,需要n+1次
* 遍历路径方法:将镜子视为节点,从当前原点节点出发,每次当前节点都需要找到下一个镜子节点(即该镜子在当前方向上,且距离最近),方法为遍历所有的镜子节点,查看两个节点构成的向量的模乘上当前目标方向得到一个向量,再加当前节点,查看是否等于下一个节点。直到找到终点或者找不到终点。
* 每一个路径遍历时会出现三种情况:
* 1.如果走了(2n+1)个镜子节点了还未到终点,则一定是进入了循环到不了终点
* 2.如果找不到下一个节点,则到不了终点
* 3.到达终点
* 由上可知路径遍历需要n^2时间复杂度
* 所以枚举算法需要n^3的时间复杂度,因为n<200,故可以接受
* */
cin>>n;
cin>>M[n+1].x>>M[n+1].y; // n+1的位置存目标
for(int i = 1;i<=n;i++) cin>>M[i].x>>M[i].y>>M[i].mirror;
if(check()){
cout<<0<<endl;
return 0;
}
else{
for(int i = 1;i<=n;i++){
change_mirror(M[i]); // 翻转镜子
if(check()){
cout<<i<<endl;
return 0;
}
change_mirror(M[i]); // 改变回来
}
}
cout<<-1<<endl;
return 0;
}