# APCS實作題2021年11月第2題:動線安排
> 日期:2023年9月21日
> 作者:王一哲
> 題目來源:110年11月實作題第2題
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=g596)
<br />
## 題目
### 問題描述
你是一個遊樂園展場的管理員,展場是一個 $m \times n$ 的矩形,可以使用木樁和線來排動線,你可以有兩種操作
- 加入木樁 r c 0。加一木樁在 $(r, c)$,並且向他的上下左右盡量找離最近的木樁連線,題目保證 $(r, c)$ 上一定沒有木樁,若有線經過則先將那些線拆掉後再來連線。
- 移除木樁 r c 1。拔掉位在 $(r, c)$ 上的木樁,並把他的線也拔掉,題目保證 $(r, c)$ 上一定有木樁。
總共有 $h$ 次操作,輸出過程中有線和有木樁佔據空間的面積最大是多少,以及 $h$ 次操作後有線和有木樁佔據空間的面積。
**註:連線可以交叉,一格上可能有兩個方向的連線。**
<br />
### 輸入說明
第一行輸入三個正整數 $m$、$n$ 和 $h$ 代表展場範圍是 $m \times n$,並且有 $h$ 筆操作。
接下來會有 $h$ 行,每一行都有三個非負整數 $r$、$c$、$t$,代表在位置 $(r, c)$ 執行操作 $t$。
數字範圍
- $1 \leq m, n \leq 100$
- $1 \leq h \leq 200$
子題配分
- 50%:$m = 1$
- 50%:無額外限制
<br />
### 輸出說明
輸出兩個數字,第一個數字表示,操作過程中有線和有木樁佔據空間的面積最大值;第二個數字表示,操作結束後有線和有木樁佔據空間的面積。
<br />
### 範例一:輸入
```
3 5 6
0 0 0
0 2 0
2 2 0
2 0 0
2 4 0
2 2 1
```
### 範例一:正確輸出
```
10
6
```
### 範例一:運算過程
用 @ 代表柱子,\* 代表連線,_ 代表沒有東西。
```cpp
add (0, 0)
@____
_____
_____
add (0, 2)
@*@__
_____
_____
add (2, 2)
@*@__
__*__
__@__
add (2, 0)
@*@__
*_*__
@*@__
add (2, 4)
@*@__
*_*__
@*@*@
remove (2, 2)
@*@__
*____
@___@
```
<br /><br />
### 範例二:輸入
```
5 5 7
2 2 0
2 4 0
4 4 0
4 0 0
0 3 0
4 3 0
4 3 1
```
### 範例二:正確輸出
```
12
7
```
<br />
### 範例二:運算過程
用 @ 代表柱子,\* 代表連線,_ 代表沒有東西。
```cpp
add (2, 2)
_____
_____
__@__
_____
_____
add (2, 4)
_____
_____
__@*@
_____
_____
add (4, 4)
_____
_____
__@*@
____*
____@
add (4, 0)
_____
_____
__@*@
____*
@***@
add (0, 3)
___@_
_____
__@*@
____*
@***@
add (4, 3)
___@_
___*_
__@*@
___**
@**@@
remove (4, 3)
___@_
_____
__@*@
____*
@___@
```
<br /><br />
## Python 程式碼
### 版本1:測試解題想法是否可行,但是程式碼太長
以下的程式碼太長,需要刪除佔了整行的註解,才能在 ZeroJudge 上測試。於 ZeroJudge 測試結果,最長運算時間約為 44 ms,使用記憶體最多約為 3.8 MB,通過測試。
```python=
m, n, h = map(int, input().split()) # 讀取m, n, h
maps = [[0]*n for _ in range(m)] # 沒有東西為0,木樁為1,横向連線為2,縱向連線為3,同時有兩個方向的連線為4
ans = 0 # 記錄木樁和連線所佔格子數量
maxAns = 0 # 記錄木樁和連線所佔格子數量最大值
""" 主要解題過程 """
for _ in range(h): # 共執行h次
r, c, t = map(int, input().split()) # 讀取r, c, t
""" 如果t為0,於 (r, c) 加上木樁 """
if t == 0:
if maps[r][c] == 0: # 如果 (r, c) 是空的
ans += 1 # 木樁和連線所佔格子數量加1
maps[r][c] = 1 # 將 (r, c) 設定為1
between = [] # 記錄兩根木樁之間的格子座標
linked = False # 加進來的木樁是否與另一根木樁連線
""" 檢查加入的木椿上方是否有另一根木樁 """
for i in range(r-1, -1, -1):
if maps[i][c] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
else: # 如果這個格子不是木樁
between.append([i, c]) # 將這個格子的座標加入串列 between
if linked: # 如果加入的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 0: # 如果這格是空的
maps[b[0]][b[1]] = 3 # 於這個格子加上縱向連線
ans += 1 # 木樁和連線所佔格子數量加1
elif maps[b[0]][b[1]] == 2: # 如果這格有横向連線
maps[b[0]][b[1]] = 4 # 將這格改成兩個方向的連線
""" 清空資料及重設變數 """
between.clear() # 清空串列 between
linked = False # 連線狀態重設為 False
""" 檢查加入的木椿下方是否有另一根木樁 """
for i in range(r+1, m):
if maps[i][c] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
else: # 如果這個格子不是木樁
between.append([i, c]) # 將這個格子的座標加入串列 between
if linked: # 如果加入的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 0: # 如果這格是空的
maps[b[0]][b[1]] = 3 # 於這個格子加上縱向連線
ans += 1 # 木樁和連線所佔格子數量加1
elif maps[b[0]][b[1]] == 2: # 如果這格有横向連線
maps[b[0]][b[1]] = 4 # 將這格改成兩個方向的連線
""" 清空資料及重設變數 """
between.clear() # 清空串列 between
linked = False # 連線狀態重設為 False
""" 檢查加入的木椿左側是否有另一根木樁 """
for i in range(c-1, -1, -1):
if maps[r][i] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
else: # 如果這個格子不是木樁
between.append([r, i]) # 將這個格子的座標加入串列 between
if linked: # 如果加入的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 0: # 如果這格是空的
maps[b[0]][b[1]] = 2 # 於這個格子加上横向連線
ans += 1 # 木樁和連線所佔格子數量加1
elif maps[b[0]][b[1]] == 3: # 如果這格有縱向連線
maps[b[0]][b[1]] = 4 # 將這格改成兩個方向的連線
""" 清空資料及重設變數 """
between.clear() # 清空串列 between
linked = False # 連線狀態重設為 False
""" 檢查加入的木椿右側是否有另一根木樁 """
for i in range(c+1, n):
if maps[r][i] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
else: # 如果這個格子不是木樁
between.append([r, i]) # 將這個格子的座標加入串列 between
if linked: # 如果加入的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 0: # 如果這格是空的
maps[b[0]][b[1]] = 2 # 於這個格子加上横向連線
ans += 1 # 木樁和連線所佔格子數量加1
elif maps[b[0]][b[1]] == 3: # 如果這格有縱向連線
maps[b[0]][b[1]] = 4 # 將這格改成兩個方向的連線
""" 如果t為1,移除 (r, c) 的木樁 """
if t == 1:
maps[r][c] = 0 # 將 (r, c) 設定為0
between = [] # 記錄兩根木樁之間的格子座標
linked = False # 被移除的木樁是否與另一根木樁連線
ans -= 1 # 木樁和連線所佔格子數量減1
""" 檢查被移除的木椿上方是否有另一根木樁 """
for i in range(r-1, -1, -1):
if maps[i][c] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
elif maps[i][c] == 3 or maps[i][c] == 4: # 如果格子上有縱向或兩個方向的連線
between.append([i, c]) # 將這個格子的座標加入串列 between
if linked: # 如果被移除的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 3: # 如果此格是縱向連線
maps[b[0]][b[1]] = 0 # 拆掉連線,將此格設定為0
ans -= 1 # 木樁和連線所佔格子數量減1
elif maps[b[0]][b[1]] == 4: # 如果此格是兩個方向的連線
maps[b[0]][b[1]] = 2 # 只剩下横向連線
""" 清空資料及重設變數 """
between.clear() # 清空串列 between
linked = False # 連線狀態重設為 False
""" 檢查被移除的木椿下方是否有另一根木樁 """
for i in range(r+1, m):
if maps[i][c] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
elif maps[i][c] == 3 or maps[i][c] == 4: # 如果格子上有縱向或兩個方向的連線
between.append([i, c]) # 將這個格子的座標加入串列 between
if linked: # 如果被移除的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 3: # 如果此格是縱向連線
maps[b[0]][b[1]] = 0 # 拆掉連線,將此格設定為0
ans -= 1 # 木樁和連線所佔格子數量減1
elif maps[b[0]][b[1]] == 4: # 如果此格是兩個方向的連線
maps[b[0]][b[1]] = 2 # 只剩下横向連線
""" 清空資料及重設變數 """
between.clear() # 清空串列 between
linked = False # 連線狀態重設為 False
""" 檢查被移除的木椿左側是否有另一根木樁 """
for i in range(c-1, -1, -1):
if maps[r][i] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
elif maps[r][i] == 2 or maps[r][i] == 4: # 如果格子上有横向或兩個方向的連線
between.append([r, i]) # 將這個格子的座標加入串列 between
if linked: # 如果被移除的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 2: # 如果此格是横向連線
maps[b[0]][b[1]] = 0 # 拆掉連線,將此格設定為0
ans -= 1 # 木樁和連線所佔格子數量減1
elif maps[b[0]][b[1]] == 4: # 如果此格是兩個方向的連線
maps[b[0]][b[1]] = 3 # 只剩下縱向連線
""" 清空資料及重設變數 """
between.clear() # 清空串列 between
linked = False # 連線狀態重設為 False
""" 檢查被移除的木椿右側是否有另一根木樁 """
for i in range(c+1, n):
if maps[r][i] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
elif maps[r][i] == 2 or maps[r][i] == 4: # 如果格子上有横向或兩個方向的連線
between.append([r, i]) # 將這個格子的座標加入串列 between
if linked: # 如果被移除的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 2: # 如果此格是横向連線
maps[b[0]][b[1]] = 0 # 拆掉連線,將此格設定為0
ans -= 1 # 木樁和連線所佔格子數量減1
elif maps[b[0]][b[1]] == 4: # 如果此格是兩個方向的連線
maps[b[0]][b[1]] = 3 # 只剩下縱向連線
maxAns = max(maxAns, ans) # 更新木樁和連線所佔格子數量最大值
""" 除錯用,印出目前的地圖 """
"""for i in range(m):
for j in range(n):
print(maps[i][j], end=" " if j < n-1 else "\n")"""
""" 除錯用,印出木樁和連線所佔格子數量最大值及目前所佔格子數 """
#print("max = {:d}, area = {:d}".format(maxAns, ans))
""" 印出答案 """
print(maxAns)
print(ans)
```
<br /><br />
### 版本2:解題想法與版本1相同,但是用自訂函式減少程式碼行數
於 ZeroJudge 測試結果,最長運算時間約為 38 ms,使用記憶體最多約為 3.7 MB,通過測試。
```python=
m, n, h = map(int, input().split()) # 讀取m, n, h
maps = [[0]*n for _ in range(m)] # 沒有東西為0,木樁為1,横向連線為2,縱向連線為3,同時有兩個方向的連線為4
ans = 0 # 記錄木樁和連線所佔格子數量
maxAns = 0 # 記錄木樁和連線所佔格子數量最大值
""" 自訂函式:加入木樁時檢查縱向是否有其它木樁或連線 """
def checkCol(c, ri, rf, d):
global maps, ans
between = [] # 記錄兩根木樁之間的格子座標
linked = False # 加進來的木樁是否與另一根木樁連線
for i in range(ri, rf, d):
if maps[i][c] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
else: # 如果這個格子不是木樁
between.append([i, c]) # 將這個格子的座標加入串列 between
if linked: # 如果加入的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 0: # 如果這格是空的
maps[b[0]][b[1]] = 3 # 於這個格子加上縱向連線
ans += 1 # 木樁和連線所佔格子數量加1
elif maps[b[0]][b[1]] == 2: # 如果這格有横向連線
maps[b[0]][b[1]] = 4 # 將這格改成兩個方向的連線
""" 自訂函式:加入木樁時檢查橫向是否有其它木樁或連線 """
def checkRow(r, ci, cf, d):
global maps, ans
between = [] # 記錄兩根木樁之間的格子座標
linked = False # 加進來的木樁是否與另一根木樁連線
for i in range(ci, cf, d):
if maps[r][i] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
else: # 如果這個格子不是木樁
between.append([r, i]) # 將這個格子的座標加入串列 between
if linked: # 如果加入的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 0: # 如果這格是空的
maps[b[0]][b[1]] = 2 # 於這個格子加上横向連線
ans += 1 # 木樁和連線所佔格子數量加1
elif maps[b[0]][b[1]] == 3: # 如果這格有縱向連線
maps[b[0]][b[1]] = 4 # 將這格改成兩個方向的連線
""" 自訂函式:移除木樁時檢查縱向是否有其它木樁或連線 """
def removeCol(c, ri, rf, d):
global maps, ans
between = [] # 記錄兩根木樁之間的格子座標
linked = False # 被移除的木樁是否與另一根木樁連線
for i in range(ri, rf, d):
if maps[i][c] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
elif maps[i][c] == 3 or maps[i][c] == 4: # 如果格子上有縱向或兩個方向的連線
between.append([i, c]) # 將這個格子的座標加入串列 between
if linked: # 如果被移除的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 3: # 如果此格是縱向連線
maps[b[0]][b[1]] = 0 # 拆掉連線,將此格設定為0
ans -= 1 # 木樁和連線所佔格子數量減1
elif maps[b[0]][b[1]] == 4: # 如果此格是兩個方向的連線
maps[b[0]][b[1]] = 2 # 只剩下横向連線
""" 自訂函式:移除木樁時檢查横向是否有其它木樁或連線 """
def removeRow(r, ci, cf, d):
global maps, ans
between = [] # 記錄兩根木樁之間的格子座標
linked = False # 被移除的木樁是否與另一根木樁連線
for i in range(ci, cf, d):
if maps[r][i] == 1: # 如果遇到另一根木樁
linked = True # linked 設定為 True
break # 中止迴圈
elif maps[r][i] == 2 or maps[r][i] == 4: # 如果格子上有横向或兩個方向的連線
between.append([r, i]) # 將這個格子的座標加入串列 between
if linked: # 如果被移除的木樁與另一根木樁連線
for b in between: # 取出兩根木樁之間的格子座標
if maps[b[0]][b[1]] == 2: # 如果此格是横向連線
maps[b[0]][b[1]] = 0 # 拆掉連線,將此格設定為0
ans -= 1 # 木樁和連線所佔格子數量減1
elif maps[b[0]][b[1]] == 4: # 如果此格是兩個方向的連線
maps[b[0]][b[1]] = 3 # 只剩下縱向連線
""" 主要解題過程 """
for _ in range(h): # 共執行h次
r, c, t = map(int, input().split()) # 讀取r, c, t
""" 如果t為0,於 (r, c) 加上木樁 """
if t == 0:
if maps[r][c] == 0: # 如果 (r, c) 是空的
ans += 1 # 木樁和連線所佔格子數量加1
maps[r][c] = 1 # 將 (r, c) 設定為1
checkCol(c, r-1, -1, -1) # 檢查加入的木椿上方是否有另一根木樁
checkCol(c, r+1, m, 1) # 檢查加入的木椿下方是否有另一根木樁
checkRow(r, c-1, -1, -1) # 檢查加入的木椿左側是否有另一根木樁
checkRow(r, c+1, n, 1) # 檢查加入的木椿右側是否有另一根木樁
""" 如果t為1,移除 (r, c) 的木樁 """
if t == 1:
maps[r][c] = 0 # 將 (r, c) 設定為0
ans -= 1 # 木樁和連線所佔格子數量減1
removeCol(c, r-1, -1, -1) # 檢查被移除的木椿上方是否有另一根木樁
removeCol(c, r+1, m, 1) # 檢查被移除的木椿下方是否有另一根木樁
removeRow(r, c-1, -1, -1) # 檢查被移除的木椿左側是否有另一根木樁
removeRow(r, c+1, n, 1) # 檢查被移除的木椿右側是否有另一根木樁
maxAns = max(maxAns, ans) # 更新木樁和連線所佔格子數量最大值
""" 除錯用,印出目前的地圖 """
"""for i in range(m):
for j in range(n):
print(maps[i][j], end=" " if j < n-1 else "\n")"""
""" 除錯用,印出木樁和連線所佔格子數量最大值及目前所佔格子數 """
#print("max = {:d}, area = {:d}".format(maxAns, ans))
""" 印出答案 """
print(maxAns)
print(ans)
```
<br /><br />
___
###### tags:`APCS`、`Python`