# APCS 202306 第2題 特殊位置 (ZeroJudge k732)
[ZJ題目連結](https://zerojudge.tw/ShowProblem?problemid=k732) (此題解先說明方法再揭示範例程式,包含python與C++)
## 方法說明
對於一個$n\times m$的二維陣列(矩陣)$a$,這題要計算那些是所謂的*特殊點*,最後輸出特殊點的個數以及每一個特殊點的位置。
設 $x = a[i][j]$,離$(i,j)$曼哈頓距離為$x$內的點數值總和 % 10 恰為 $x\%10$的稱之為特殊位置。定義兩個點$(a, b)$ 和 $(c, d)$ 的曼哈頓距離為 $|a - c| + |b - d|$。
**這題只要枚舉就可以了**。
我們枚舉所有的點$x = a[i][j]$,找出所有滿足距離要求的點並將其加總,將加總的結果來判斷是否為特殊點。對於$a[i][j]$,若$a[s][t]$距離在$x$之內,則必然 $i-x\leq s\leq i+x$且$j-x\leq t\leq j+x$。需要注意的是上述條件反之未必然,有可能超出界線或距離超過$x$,所以我們雙迴圈枚舉在此範圍的點,並且以if來判斷是否合乎距離需求。
對於輸出,因為需要先輸出點數再輸出各點位置,我們必須先將找出的點放在一個容器(list or array)中,題目要求的順序是*字典序*,也就是row-major,由上而下,由左至右。我們便依此順序來枚舉。主要流程如下:
```
輸入矩陣
準備一個放答案的容器ans
for i from 0 to n-1
for j from 0 to m-1
x = a[i][j]
total = 0 用來加總的變數
for s from i-x to i+x
for t from j-x to j+x
if s,t未出界 且 abs(i-s)+abs(j-t)<=x then
total += a[s][t]
end if
end for-t
end for-s
if total%10 == x%10 then
將(i,j)放入ans中
end if
end for j
end for-i
輸出答案
```
**小技巧:** 為什麼枚舉就可以呢?因為是APCS的第二題不會太需要時間複雜度的思考(大誤)。上面是開玩笑,說正格的,這題n,m不超過50,矩陣元素$x$不超過9,我們可以估算最多需要加總的個數是$50\times 50\times 19\times 19 < 10^6$,現在一般電腦一秒鐘python大概$10^6$,C++ $10^7$這樣的運算不會有問題。
這題是可以算得技巧一些來加快速度,但並不需要。
## 第一子題
雖然這題不困難,我們還是講一下第一子題的範例程式。第一子題是一維陣列(n=1),因此對於任何一點,只需要用一層迴圈找出左右範圍即可,曼哈頓距離也不需要計算。以下是Python與C++的範例程式。
### Python code
```python=
n,m = map(int,input().split())
# n= 1
mat = [int(x) for x in input().split()]
point = []
for j in range(m):
x = mat[j]
total = 0
for t in range(j-x,j+x+1):
if 0<=t<m:
total += mat[t]
# total = sum(mat[max(0,j-x):min(m,j+x+1)])
if (total-x)%10==0:
point.append(j)
print(len(point))
for x in point:
print(0,x)
```
其中第7~10行的程式也可以寫作第11行的寫法。
第3行輸入一行空白間隔的整數的方法初學者需要學一下。
留意list加入一個元素用append(第13行),list的元素個數可以用len(pooint)求出(第15行)。
### C code
這題C與C++幾乎沒有甚麼差異,以下為第一子題C的範例程式。
```c=
// s1, row=1
#include <stdio.h>
int main() {
int n,m, i,j,a[101];
int sol[101],k=0;
scanf("%d%d",&n,&m); //n==1
for (i=0; i<m; i++) scanf("%d",&a[i]);
for (i=0; i<m; i++) {
int total=0,first=i-a[i],last=i+a[i];
if (first<0) first = 0;
if (last>=m) last = m-1;
for (j=first; j<=last; ++j) total += a[j];
if ((total-a[i])%10 == 0) {
sol[k++] = i;
}
}
// output
printf("%d\n",k);
for (i=0;i<k;i++) printf("0 %d\n",sol[i]);
return 0;
}
```
第9~11行決定要加總的範圍後,第12行做加總,初學者可留意第14行將答案放入陣列中的方法,這裡的k就是陣列sol中元素的個數。
## 完全解
以下是Python完全解的範例程式。第4行每次讀入一行空白間隔整數並將它放在list中。第17行將點座標放入point中,可以用tuple (i,j)或是list [i,j]。第20行將座標從point中取出列印時,可以直接這樣寫
for r,c in point:
point中每個元素都是2個元素的tuple,例如(0,3),取出時會自動unpacking執行 r=0; c=3。
### Python code
```python=
n,m = map(int,input().split())
mat = []
for i in range(n):
mat.append([int(x) for x in input().split()])
point = [] # answer
for i in range(n):
for j in range(m):
x = mat[i][j]
total = 0
#enumerate all (s,t) in range
for s in range(i-x,i+x+1):
for t in range(j-x,j+x+1):
if 0<=s<n and 0<=t<m and abs(i-s)+abs(j-t)<=x:
total += mat[s][t]
if (total-x)%10==0:
point.append((i,j))
# output answer
print(len(point))
for r,c in point:
print(r,c)
```
### C++ code
接著是C++的範例程式。
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, i,j,m,r,c;
int a[50][50];
int srow[2500],scol[2500],k=0;
cin >> n>> m;
for (i=0;i<n;i++) {
for (j=0;j<m;j++) {
cin >> a[i][j];
}
}
for (r=0; r<n; r++) {
for (c=0; c<m; c++) {
int total=0,x=a[r][c];
for (i=r-x; i<=r+x; i++){
if (i<0 || i>=n) continue;
for (j=c-x; j<=c+x; j++) {
if (j<0 || j>=m) continue;
if (abs(i-r)+abs(c-j)<=x)
total += a[i][j];
}
}
if ((total-x)%10==0) {
srow[k]=r,scol[k]=c;
k++;
}
}
}
// output
cout <<k<<'\n';
for (i=0;i<k;i++) {
cout << srow[i]<<' '<<scol[i]<<'\n';
}
return 0;
}
```
###### tags: `apcs`