题目说明:
题目给定n行m列的镜子,字符表示为
‘\' ,'/' :表示斜着的镜子,可以反射四个方向
'-' , '|',表示横着,竖着的镜子,可以反射两个方向
注: (碰到边界则光线将消失)
将给定个q,表示q次操作,每次操作如下:
每次操作将一束光线从坐标(x,y)处发射
方向为{above,below,left,right}的其中一种
例如{1,2,below}表示从坐标为(1,2)处发射一个方向向下的光线
求:最终光线能从多少个不同的镜子反射
说明:思路实际上并不难,因为光线最终只有两种可能,一种是链状,一种是环状,困难的地方在于实现细节,因此本篇重点从实现细节入手。
不同点:该题目比较特殊的地方是每个坐标都有四个方向,因此相对于普通的搜索,我们还需要考虑方向
一.使用的变量记录
将代码中用到的变量记录下来,防止有人不知道变量的含义
int c[1005][1005];//记录图,'\','/','-','|'分别表示0,1,2,3
int ans[1005][1005][4];//表示为坐标为x,y时,接收方向为k的光线的答案
int target[4][4] = { 2,3,0,1,3,2,1,0,1,0,2,3,0,1,3,2 };//用于记录反射后的光线
int mx[4] = {-1,1,0,0};//改变的x坐标
int my[4] = { 0,0,-1,1 };//改变的y坐标
bool st[1005][1005];//坐标(x,y)是否加入过答案中
bool vis[1005][1005][4];//记录坐标(x,y),接收方向为k的光线是否跑过dfs
int in[1005][1005][4];//表示坐标为(x,y),接收方向为k的光线的入度
set<pair<int, int>>s;//记录走过的反射点坐标,利用set可去重
二.区分出链状与环状
1.可以参考拓补排序,让每个坐标被射入4个方向的射线,看看能够反弹到哪里,反弹到的位置入度+1
//0,1,2,3分别表示上下左右
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int k = 0; k < 4; k++) {
int tk = target[c[i][j]][k];//tk表示光线经过该镜子后的方向
int tx = i + mx[tk];//下一步的x坐标
int ty = j + my[tk];//下一步的y坐标
in[tx][ty][tk]++;
}
}
}
2.此后对入度为0的点,跑dfs,因为环状光线中,不会存在入度为0的点,实际上该步骤是在跑链状光线
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int k = 0; k < 4; k++) {
if (in[i][j][k] != 0)continue;
dfs(i, j, k);
//跑完dfs需要清空记录的反射点
for (auto x : s) {
st[x.first][x.second] = 0;
}
s.clear();
}
}
}
3. 跑完链状光线后,剩下未跑的点都是环状光线的。因为跑dfs时,用vis记录过跑过的点,因此仅需跑vis值为0的点,也就是未跑的点
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int k = 0; k < 4; k++) {
if (!vis[i][j][k]) {
s.clear();//s记录每次dfs过程中的反射点,要初始化
dfs(i, j, k);
}
}
}
}
三.dfs过程中的细节
dfs实际上可以分成链状的dfs与环状的dfs,
1.对于链状的dfs,我们需要跑到结尾时,再倒着得到答案,并且需要记录反射点,遇到相同的反射点答案是不能增加的
如下图,共发生三次反射,可以看作是终点处,倒着遇到每个(不同)反射点,该点ans[now]=ans[last]+1,最终可以得到该链上每个点的答案
void dfs1(int x, int y, int k) {
if (x == 0 || x == n + 1 || y == 0 || y == m + 1) {
return;
}
vis[x][y][k] = 1;
int tk = target[c[x][y]][k];
int tx = x + mx[tk];
int ty = y + my[tk];//求出下一步的目的坐标与光线方向
int plus = 0;
dfs1(tx, ty, tk); //一定要先dfs到结尾,再倒着记录ans
//发生反射,且当前反射点未用过才+1
if (!st[x][y] && tk != k) {
st[x][y] = 1;
plus++;
}
ans[x][y][k] = ans[tx][ty][tk] + plus;
}
2.而对于环状dfs,环中每个结点的大小均为环中反射点的数量,因此仅需用set(可以自动去重)记录每个反射点,最终令结点均等于set.size大小即可
int dfs2(int x, int y, int k) {
if (vis[x][y][k])return s.size();
vis[x][y][k] = 1;
int tk = target[c[x][y]][k];
int tx = x + mx[tk];
int ty = y + my[tk];//求出下一步的目的坐标与光线方向
if (tk != k) {
s.insert({ x,y });
}
int temp = dfs(tx, ty, tk);
//结点上的每个点答案均为(环中反射点数量),也就是s.size()
return ans[x][y][k] = temp;
}
四.输出答案
由于每次操作给的是,从坐标(x,y)射出某方向的光线,而非被射入,因此需要将x,y改为下一步的坐标
for (int i = 1; i <= q; i++) {
int x, y;
string s;
cin >> x >> y >> s;
if (s == "above") {
cout << ans[x - 1][y][0] << endl;
}
else if (s == "below") {
cout << ans[x + 1][y][1] << endl;
}
else if (s == "left") {
cout << ans[x][y - 1][2] << endl;
}
else cout << ans[x][y + 1][3] << endl;
}