LCP 27. 黑盒光线反射
秋日市集上有个奇怪的黑盒,黑盒的主视图为 n*m 的矩形。从黑盒的主视图来看,黑盒的上面和下面各均匀分布有 m 个小孔,黑盒的左面和右面各均匀分布有 n 个小孔。黑盒左上角小孔序号为 0,按顺时针编号,总共有 2(m+n) 个小孔。每个小孔均可以打开或者关闭,初始时,所有小孔均处于关闭状态。每个小孔上的盖子均为镜面材质。例如一个 2\3 的黑盒主视图与其小孔分布如图所示:
{:height="200px"}
店长告诉小扣,这里是「几何学的快问快答」,店长可能有两种操作:
open(int index, int direction)
- 若小孔处于关闭状态,则打开小孔,照入光线;否则直接照入光线;close(int index)
- 关闭处于打开状态小孔,店长保证不会关闭已处于关闭状态的小孔;其中:
index
: 表示小孔序号direction
:1
表示光线沿 $y=x$ 方向,-1
表示光线沿 $y=-x$ 方向。{:height="200px"}
当光线照至边界时:若边界上的小孔为开启状态,则光线会射出;否则,光线会在小孔之间进行反射。特别地:
-1
){:height="200px"}
请帮助小扣判断并返回店长每次照入的光线从几号小孔射出。
示例 1:
输入:
["BlackBox","open","open","open","close","open"]
[[2,3],[6,-1],[4,-1],[0,-1],[6],[0,-1]]
输出:
[null,6,4,6,null,4]
解释: BlackBox b = BlackBox(2,3); // 新建一个 2x3 的黑盒 b.open(6,-1) // 打开 6 号小孔,并沿 y=-x 方向照入光线,光线至 0 号小孔反射,从 6 号小孔射出 b.open(4,-1) // 打开 4 号小孔,并沿 y=-x 方向照入光线,光线轨迹为 4-2-8-2-4,从 4 号小孔射出 b.open(0,-1) // 打开 0 号小孔,并沿 y=-x 方向照入光线,由于 6 号小孔为开启状态,光线从 6 号小孔射出 b.close(6) // 关闭 6 号小孔 b.shoot(0,-1) // 从 0 号小孔沿 y=-x 方向照入光线,由于 6 号小孔为关闭状态,4 号小孔为开启状态,光线轨迹为 0-6-4,从 4 号小孔射出
示例 2:
输入:
["BlackBox","open","open","open","open","close","open","close","open"]
[[3,3],[1,-1],[5,1],[11,-1],[11,1],[1],[11,1],[5],[11,-1]]
输出:
[null,1,1,5,1,null,5,null,11]
解释:
{:height="300px"}
BlackBox b = BlackBox(3,3); // 新建一个 3x3 的黑盒 b.open(1,-1) // 打开 1 号小孔,并沿 y=-x 方向照入光线,光线轨迹为 1-5-7-11-1,从 1 号小孔射出 b.open(5,1) // 打开 5 号小孔,并沿 y=x 方向照入光线,光线轨迹为 5-7-11-1,从 1 号小孔射出 b.open(11,-1) // 打开 11 号小孔,并沿逆 y=-x 方向照入光线,光线轨迹为 11-7-5,从 5 号小孔射出 b.open(11,1) // 从 11 号小孔沿 y=x 方向照入光线,光线轨迹为 11-1,从 1 号小孔射出 b.close(1) // 关闭 1 号小孔 b.open(11,1) // 从 11 号小孔沿 y=x 方向照入光线,光线轨迹为 11-1-5,从 5 号小孔射出 b.close(5) // 关闭 5 号小孔 b.open(11,-1) // 从 11 号小孔沿 y=-x 方向照入光线,光线轨迹为 11-1-5-7-11,从 11 号小孔射出
提示:
1 <= n, m <= 10000
1 <= 操作次数 <= 10000
direction
仅为 1
或 -1
0 <= index < 2*(m+n)
原站题解
golang 解法, 执行用时: 1092 ms, 内存消耗: 8.3 MB, 提交时间: 2023-10-08 22:57:39
type BlackBox struct { Hole []bool P []int N []int T int L int M int Num int } func Constructor(n int, m int) BlackBox { Box := new(BlackBox) Box.M = m Box.Num = n l := 2*m + 2*n Box.L = l Box.Hole = make([]bool, Box.L) Box.T = 0 Box.P = make([]int, Box.L) Box.N = make([]int, Box.L) for i := 0; i < l; i++ { Box.P[i] = (l - i) % l Box.N[i] = (m * 2 - i + l) % l } return *Box } func (this *BlackBox) Open(index int, direction int) int { if !this.Hole[index] { this.T ++ this.Hole[index] = true } if this.T == 1 { return index } else { for { if direction == 1 { index = this.P[index] } else { index = this.N[index] } direction = -direction if this.Hole[index] { break; } } } return index } func (this *BlackBox) Close(index int) { this.Hole[index] = false this.T-- } /** * Your BlackBox object will be instantiated and called as such: * obj := Constructor(n, m); * param_1 := obj.Open(index,direction); * obj.Close(index); */
python3 解法, 执行用时: 840 ms, 内存消耗: 71 MB, 提交时间: 2023-10-08 22:57:05
from sortedcontainers import SortedList class BlackBox: def __init__(self, n: int, m: int): corners, T = {0, m, m + n, m + n + m, m + n + m + n}, m + n + m + n def getnext(idx, dr): if dr > 0: nid = T - idx else: nid = (T - (idx + n) - n) % T ndr = dr if nid in corners else -dr return nid, ndr seen = set() glen, gid, node_book = [0] * (m + n + m + n), 0, dict() for cur in product(range(m + n + m + n), (1, -1)): if cur in seen: continue while cur not in seen: seen.add(cur) node_book[cur] = (gid, glen[gid]); glen[gid] += 1 cur = getnext(*cur) gid += 1 self.node_book, self.opened_book = node_book, [SortedList() for _ in range(gid)] def open(self, index: int, dr: int) -> int: for dr in (-dr, dr): gid, oid = self.node_book[index, dr] if (oid, index) not in self.opened_book[gid]: self.opened_book[gid].add((oid, index)) rid = self.opened_book[gid].bisect_right((oid, index)) % len(self.opened_book[gid]) res = self.opened_book[gid][rid][1] return res def close(self, index: int) -> None: for dr in (1, -1): gid, oid = self.node_book[index, dr] self.opened_book[gid].remove((oid, index)) # Your BlackBox object will be instantiated and called as such: # obj = BlackBox(n, m) # param_1 = obj.open(index,direction) # obj.close(index)
java 解法, 执行用时: 1110 ms, 内存消耗: 53 MB, 提交时间: 2023-10-08 22:55:36
public class BlackBox { boolean[] state;// 存储index下标小洞的打开状态:false表示关闭,true表示打开 int[] clock;// 存储index小洞沿y=x方向将要照射的下一个小洞的index int[] anticlock;// 存储index小洞沿y=-x方向将要照射的下一个小洞的index BlackBox(int n, int m) { int cnt = 2 * (m + n); state = new boolean[cnt]; clock = new int[cnt]; anticlock = new int[cnt]; for (int index = 0; index < cnt; index++) { clock[index] = (cnt - index + cnt) % cnt; anticlock[index] = (2 * m - index + cnt) % cnt; state[index] = false; } } public int open(int index, int direction) { int I = index;// 保存起点 state[index] = true;// 打开小洞 // 选择下一个反射点 if (direction == 1) index = clock[index]; else index = anticlock[index]; // 改变反射方向 direction *= -1; // 反射操作尽量不要封装成函数,否则容易超时 while (state[index] == false) {// 若该点是闭合状态,则继续反射 // 如果下一个点是角,则光最终会原路返回起点 if (clock[index] == index || anticlock[index] == index) return I; // 选择下一个反射点 if (direction == 1) index = clock[index]; else index = anticlock[index]; // 改变反射方向 direction *= -1; } return index;// 若该点是打开状态,则从该点射出 } public void close(int index) { state[index] = false; } }; /** * Your BlackBox object will be instantiated and called as such: * BlackBox obj = new BlackBox(n, m); * int param_1 = obj.open(index,direction); * obj.close(index); */
cpp 解法, 执行用时: 184 ms, 内存消耗: 75.1 MB, 提交时间: 2023-10-08 22:55:04
class BlackBox { private: // 存储从每个小孔以 y=x 方向射出时,所在的循环的 id 以及在循环中的 id vector<pair<int, int>> groupPos; // 存储从每个小孔以 y=-x 方向射出时,所在的循环的 id 以及在循环中的 id vector<pair<int, int>> groupNeg; // 存储每个循环的有序映射 vector<map<int, int>> groupStats; public: BlackBox(int n, int m) { int ptCount = (n + m) * 2; groupPos.assign(ptCount, {-1, -1}); groupNeg.assign(ptCount, {-1, -1}); for (int i = 0; i < ptCount; ++i) { // 如果不是左上角或者右下角的小孔,那么从 y=x 方向射出找循环 if (i != 0 && i != m + n && groupPos[i].first == -1) { createGroup(n, m, i, 1); } // 如果不是左下角或者右上角的小孔,那么从 y=-x 方向射出找循环 if (i != m && i != m * 2 + n && groupNeg[i].first == -1) { createGroup(n, m, i, -1); } } } void createGroup(int n, int m, int index, int direction) { int groupId = groupStats.size(); int groupLoc = 0; groupStats.emplace_back(); // 不断模拟光线的路径,直到走到一个已经遇见过的状态,这样就找到了一个循环 while (!(direction == 1 && groupPos[index].first != -1) && !(direction == -1 && groupNeg[index].first != -1)) { if (direction == 1) { groupPos[index] = {groupId, groupLoc++}; index = (n + m) * 2 - index; } else { groupNeg[index] = {groupId, groupLoc++}; if (index <= m * 2) { index = m * 2 - index; } else { index = (m * 2 + n) * 2 - index; } } // 如果小孔不在角上,就改变方向 if (index != 0 && index != m && index != m + n && index != m * 2 + n) { direction = -direction; } } } int open(int index, int direction) { // 插入二元组 if (auto [groupId, groupLoc] = groupPos[index]; groupId != -1) { groupStats[groupId].emplace(groupLoc, index); } if (auto [groupId, groupLoc] = groupNeg[index]; groupId != -1) { groupStats[groupId].emplace(groupLoc, index); } // 查询 auto [groupId, groupLoc] = (direction == 1 ? groupPos[index] : groupNeg[index]); auto& store = groupStats[groupId]; if (auto iter = store.upper_bound(groupLoc); iter != store.end()) { return iter->second; } else { return store.begin()->second; } } void close(int index) { // 删除二元组 if (auto [groupId, groupLoc] = groupPos[index]; groupId != -1) { groupStats[groupId].erase(groupLoc); } if (auto [groupId, groupLoc] = groupNeg[index]; groupId != -1) { groupStats[groupId].erase(groupLoc); } } }; /** * Your BlackBox object will be instantiated and called as such: * BlackBox* obj = new BlackBox(n, m); * int param_1 = obj->open(index,direction); * obj->close(index); */