# APCS實作題2022年6月第3題:雷射測試
> 日期:2024年8月8日
> 作者:王一哲
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=i401)
## 題目
### 問題描述
一個雷射光從 $(0,0)$ 向右邊發射,平面上有很多個鏡子,問雷射光會反射幾次,保證輸入沒有無限循環的情形。
鏡子用三個數字表示 $(x_i, y_i, t_i)$,代表座標在 $(x_i, y_i)$ 上。$t_i = 0$ 代表示這種 / 擺放方式的鏡子,$t_i = 1$ 代表這種 \ 擺放方式的鏡子,保證不會有一個位置有多個鏡子。
<img style="display: block; margin-left: auto; margin-right: auto" height="40%" width="40%" src="https://i.imgur.com/cIaeuCg.png">
<div style="text-align:center">輸入範例1圖示</div>
<br /><br />
### 輸入說明
輸入一個正整數 $n ~(1 \leq n \leq 250000)$,代表鏡子的數量,接下來有 $n$ 行,第 $i$ 行有三個數字 $x_i$、$y_i$ 和 $t_i$。
子題配分
- 20%:$1 \leq n \leq 1000,~ 1 \leq x_i \leq 100,~ -100 \leq y_i \leq 100$
- 80%:$1 \leq n \leq 250000,~ 1 \leq x_i \leq 30000,~ -30000 \leq y_i \leq 30000$
<br />
### 輸出格式
輸出雷射光共反射幾次。
<br />
### 範例輸入1
```
10
2 0 1
2 -1 1
7 -1 0
7 2 1
4 2 0
4 1 0
3 1 1
3 2 0
1 -1 1
1 4 0
```
### 範例輸出1
```
9
```
### 範例輸入2
```
4
2 1 0
5 -3 1
4 2 1
6 -2 0
```
### 範例輸出2
```
0
```
<br /><br />
## Python 程式碼
用字典儲存排序後的鏡子資料,再用二分搜尋法找下一面鏡子,如果沒有找到符合規則的鏡子就中止迴圈,印出答案。沒有用到比較進階的語法或資料結構,花費時間較長,使用比較多的記憶體,但是程式碼比較好懂。費時最久約為 1.9 s,使用記憶體最多約為 46.4 MB,通過測試。
```python=
from bisect import bisect_left # 引入二分搜尋法函式庫
n = int(input()) # 鏡子數量
xs = dict() # 在指定的 x 座標上的鏡子資料,內容為 [(yi, ti)]
ys = dict() # 在指定的 y 座標上的鏡子資料,內容為 [(xi, ti)]
for _ in range(n): # 讀取 n 行資料
xi, yi, ti = map(int, input().split()) # 讀取 xi, yi, ti
if xi not in xs: xs[xi] = [(yi, ti)] # 如果 xi 不在 xs 之中,新增 xi 對應的資料,串列中只有一組 (yi, ti)
else: xs[xi].append((yi, ti)) # 如果 xi 已經在 xs 之中,xi 對應的資料再加上一組 (yi, ti)
if yi not in ys: ys[yi] = [(xi, ti)] # 如果 yi 不在 ys 之中,新增 yi 對應的資料,串列中只有一組 (xi, ti)
else: ys[yi].append((xi, ti)) # 如果 yi 已經在 ys 之中,yi 對應的資料再加上一組 (xi, ti)
for y in xs.values(): y.sort() # 依序取出 xs 中的資料,由小到大排序
for x in ys.values(): x.sort() # 依序取出 ys 中的資料,由小到大排序
dirs = ((1, 3), (0, 2), (3, 1), (2, 0)) # 原來的方向對應的反射後方向,0右,1上,2左,3下
d = 0 # 目前的方向,預設為0右
ans = 0 # 答案,反射次數
xc, yc = 0, 0 # 目前的位置
xn, yn, tn = 0, 0, 0 # 下一個位置,找到的鏡子種類
while True:
if d == 0: # 向右
if yc not in ys: break # 如果 y = yc 上沒有鏡子,中止迴圈
else:
idx = bisect_left(ys[yc], (xc+1, -1)) # 找 y = yc 這行 x > xc 的鏡子
#print("d = 0, idx = ", idx)
if idx == len(ys[yc]): break # 沒有找到鏡子,中止迴圈
if ys[yc][idx][0] == xc: idx += 1 # 如果找到現在的鏡子,idx 加 1
if idx < len(ys[yc]): # 如果 idx 沒有出界,找到符合規則的鏡子
ans += 1 # 答案加 1
xn, tn = ys[yc][idx] # 更新 xn, tn
yn = yc # 更新 yn
else: break # 如果 idx 出界,中止迴圈
elif d == 2: # 向左
if yc not in ys: break # 如果 y = yc 上沒有鏡子,中止迴圈
else:
idx = bisect_left(ys[yc], (xc-1, -1)) # 找 y = yc 這行 x < xc 的鏡子
#print("d = 2, idx = ", idx)
if idx == len(ys[yc]): break # 沒有找到鏡子,中止迴圈
if ys[yc][idx][0] == xc: idx -= 1 # 如果找到現在的鏡子,idx 減 1
if idx >= 0: # 如果 idx 沒有出界,找到符合規則的鏡子
ans += 1 # 答案加 1
xn, tn = ys[yc][idx] # 更新 xn, tn
yn = yc # 更新 yn
else: break # 如果 idx 出界,中止迴圈
elif d == 1: # 向上
if xc not in xs: break # 如果 x = xc 上沒有鏡子,中止迴圈
else:
idx = bisect_left(xs[xc], (yc+1, -1)) # 找 x = xc 這行 y > yc 的鏡子
#print("d = 1, idx = ", idx)
if idx == len(xs[xc]): break # 沒有找到鏡子,中止迴圈
if xs[xc][idx][0] == yc: idx += 1 # 如果找到現在的鏡子,idx 加 1
if idx < len(xs[xc]): # 如果 idx 沒有出界,找到符合規則的鏡子
ans += 1 # 答案加 1
yn, tn = xs[xc][idx] # 更新 yn, tn
xn = xc # 更新 xn
else: break # 如果 idx 出界,中止迴圈
elif d == 3: # 向下
if xc not in xs: break # 如果 x = xc 上沒有鏡子,中止迴圈
else:
idx = bisect_left(xs[xc], (yc-1, -1)) # 找 x = xc 這行 y < yc 的鏡子
#print("d = 3, idx = ", idx)
if idx == len(xs[xc]): break # 沒有找到鏡子,中止迴圈
if xs[xc][idx][0] == yc: idx -= 1 # 如果找到現在的鏡子,idx 減 1
if idx >= 0: # 如果 idx 沒有出界,找到符合規則的鏡子
ans += 1 # 答案加 1
yn, tn = xs[xc][idx] # 更新 yn, tn
xn = xc # 更新 xn
else: break # 如果 idx 出界,中止迴圈
d = dirs[d][tn] # 更新方向
xc, yc = xn, yn # 更新 xc, yc
#print(xn, yn, tn, d, ans)
print(ans)
```
<br /><br />
## C++ 程式碼
以下的程式碼中有幾個需要注意的細節。
1. 第18、19行,要用迭代器取出指定記憶體位址的資料,使用 sort 將資料排序後才能改變字典中原來的資料順序。
2. 第31、44、57、70行,使用 lower_bound 找指定的 pair 時,要先用 make_pair 將數對組成 pair,不能直接用 {} 包起來。
費時最久約為 0.2 s,使用記憶體最多約為 9.2 MB,通過測試。
```cpp=
#include <iostream>
#include <vector>
#include <map>
#include <utility> // for pair
#include <algorithm> // for lower_bound
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0); // 加速
int n; cin >> n; // 鏡子數量
// xs 在指定的 x 座標上的鏡子資料,內容為 [(yi, ti)]。 ys 在指定的 y 座標上的鏡子資料,內容為 [(xi, ti)]
map<int, vector<pair<int, int>>> xs, ys;
for(int i=0; i<n; i++) { // 讀取 n 行資料
int xi, yi, ti; cin >> xi >> yi >> ti; // 讀取 xi, yi, ti
xs[xi].push_back({yi, ti}); // xi 對應的資料再加上一組 (yi, ti)
ys[yi].push_back({xi, ti}); // yi 對應的資料再加上一組 (xi, ti)
}
for(auto it = xs.begin(); it != xs.end(); it++) sort((*it).second.begin(), (*it).second.end()); // 依序取出 xs 中的資料,由小到大排序
for(auto it = ys.begin(); it != ys.end(); it++) sort((*it).second.begin(), (*it).second.end()); // 依序取出 ys 中的資料,由小到大排序
int dirs[4][2] = {{1, 3}, {0, 2}, {3, 1}, {2, 0}}; // 原來的方向對應的反射後方向,0右,1上,2左,3下
int d = 0; // 目前的方向,預設為0右
int ans = 0; // 答案,反射次數
int xc = 0, yc = 0; // 目前的位置
int xn = 0, yn = 0, tn = 0; // 下一個位置,找到的鏡子種類
while(true) {
if (d == 0) { // 向右
if (ys[yc].empty()) break; // 如果 y = yc 上沒有鏡子,中止迴圈
else {
int idx = lower_bound(ys[yc].begin(), ys[yc].end(), make_pair(xc+1, -1)) - ys[yc].begin(); // 找 y = yc 這行 x > xc 的鏡子
//cout << "d = 0, idx = " << idx << "\n";
if (idx == (int)ys[yc].size()) break; // 沒有找到鏡子,中止迴圈
if (ys[yc][idx].first == xc) idx++; // 如果找到現在的鏡子,idx 加 1
if (idx < (int)ys[yc].size()) { // 如果 idx 沒有出界,找到符合規則的鏡子
ans++; // 答案加 1
xn = ys[yc][idx].first; tn = ys[yc][idx].second; // 更新 xn, tn
yn = yc; // 更新 yn
} else break; // 如果 idx 出界,中止迴圈
}
} else if (d == 2) { // 向左
if (ys[yc].empty()) break; // 如果 y = yc 上沒有鏡子,中止迴圈
else {
int idx = lower_bound(ys[yc].begin(), ys[yc].end(), make_pair(xc-1, -1)) - ys[yc].begin(); // 找 y = yc 這行 x < xc 的鏡子
//cout << "d = 2, idx = " << idx << "\n";
if (idx == (int)ys[yc].size()) break; // 沒有找到鏡子,中止迴圈
if (ys[yc][idx].first == xc) idx--; // 如果找到現在的鏡子,idx 減 1
if (idx >= 0) { // 如果 idx 沒有出界,找到符合規則的鏡子
ans++; // 答案加 1
xn = ys[yc][idx].first; tn = ys[yc][idx].second; // 更新 xn, tn
yn = yc; // 更新 yn
} else break; // 如果 idx 出界,中止迴圈
}
} else if (d == 1) { // 向上
if (xs[xc].empty()) break; // 如果 x = xc 上沒有鏡子,中止迴圈
else {
int idx = lower_bound(xs[xc].begin(), xs[xc].end(), make_pair(yc+1, -1)) - xs[xc].begin(); // 找 x = xc 這行 y > yc 的鏡子
//cout << "d = 1, idx = " << idx << "\n";
if (idx == (int)xs[xc].size()) break; // 沒有找到鏡子,中止迴圈
if (xs[xc][idx].first == yc) idx++; // 如果找到現在的鏡子,idx 加 1
if (idx < (int)xs[xc].size()) { // 如果 idx 沒有出界,找到符合規則的鏡子
ans++; // 答案加 1
yn = xs[xc][idx].first; tn = xs[xc][idx].second; // 更新 yn, tn
xn = xc; // 更新 xn
} else break; // 如果 idx 出界,中止迴圈
}
} else if (d == 3) { // 向下
if (xs[xc].empty()) break; // 如果 x = xc 上沒有鏡子,中止迴圈
else {
int idx = lower_bound(xs[xc].begin(), xs[xc].end(), make_pair(yc-1, -1)) - xs[xc].begin(); // 找 x = xc 這行 y < yc 的鏡子
//cout << "d = 3, idx = " << idx << "\n";
if (idx == (int)xs[xc].size()) break; // 沒有找到鏡子,中止迴圈
if (xs[xc][idx].first == yc) idx--; // 如果找到現在的鏡子,idx 減 1
if (idx >= 0) { // 如果 idx 沒有出界,找到符合規則的鏡子
ans++; // 答案加 1
yn = xs[xc][idx].first; tn = xs[xc][idx].second; // 更新 yn, tn
xn = xc; // 更新 xn
} else break; // 如果 idx 出界,中止迴圈
}
}
d = dirs[d][tn]; // 更新方向
xc = xn; yc = yn; // 更新 xc, yc
//cout << "xn: " << xn << "yn: " << yn << "tn: " << tn << "d: " << d << "ans: " << ans << "\n";
}
cout << ans << "\n";
return 0;
}
```
<br /><br />
---
###### tags:`APCS`、`Python`、`C++`