---
# System prepended metadata

title: APCS 20191026 題解
tags: [APCS]

---

# APCS 20191026 題解
###### tags: `APCS`

以下數字範圍只列所有子題中最大的  
題目敘述有修改過

## 第一題

### 題目大意

給一個有 $n$ 項的數列 $A$，由第四項起由左而右開始變更數字，變更的方式是，若 $|A_i-A_{i-1}|>5$，就把 $A_i$ 變成 $A_{i-3}$、$A_{i-2}$、$A_{i-1}$ 的中位數，至於在 $A_i$ 之前的項的值都是考慮變更後的數字，而非原始值。

給 $n$ 和 $A$，求變更後的數列。

$1 \leq n \leq 32$  
$1 \leq A_i \leq 35$

### 解法

就掃一遍一邊改就好，超級簡單。

時間複雜度 $O(n)$。

```cpp=
#include <bits/stdc++.h>

using namespace std;

int main(){

    int n;
    cin >> n;
    vector<int> a(n);
    for(int i = 0; i < n; i++) cin >> a[i];

    for(int i = 3; i < n; i++){
        if(abs(a[i] - a[i - 1]) <= 5) continue;
        int t[3] = {a[i - 3], a[i - 2], a[i - 1]};
        sort(t, t + 3);
        a[i] = t[1];
    }

    for(int i = 0; i < n; i++){
        if(i != 0) cout << " ";
        cout << a[i];
    }
    cout << "\n";

    return 0;
}
```

## 第二題

### 題目大意

給一個有 $n$ 個數的數列 $P$，有三種操作：

1. 洗牌：把 $P$ 變成 $\{P_1,P_{n/2+1},P_{2},P_{n/2+2},...,P_{n/2},P_{n}\}$，也就是把 $P$ 平分成前後兩部分，再交錯取兩部分的第一項，放到新的數列尾端。
2. 反洗牌：把 $P$ 變成 $\{P_1,P_3,P_5,...,P_{n-1},P_{2},P_{4},...,P_n\}$，也就是把偶數項移到尾端。
3. $k$-切牌：把 $P$ 變成 $\{P_{k+1},{P_{k+2},...,P_n,P_1,P_2,...,P_k}\}$，也就是把前 $k$ 項移到尾端。（$3 \leq k < n$）。

給一個大小為 $m$ 的序列 $A$，$A_i$ 表示第 $A_i$ 個操作為何，$1$ 表示洗牌，$2$ 表示反洗牌，其餘表示 $A_i$-切牌。再給一個數列 $X$，表示 $P$ 進行完 $A$ 中的操作後的結果。求初始的 $P$。

$4 \leq n \leq 200$，$n$ 是偶數  
$1 \leq m \leq 20$

### 解法

就是把每種操作反推回來：

1. 洗牌反過來：把 $X$ 變成 $\{X_1,X_3,X_5,...,X_{n-1},X_2,X_4,...,X_n\}$，等同於把 $X$ 反洗牌。
2. 反洗牌反過來：把 $X$ 變成 $\{X_1,X_{n/2+1},X_{2},X_{n/2+2},...,X_{n/2},X_{n}\}$，也就是把 $X$ 洗牌。
3. $k$-切牌反過來：把 $X$ 變成 $\{P_{n-k+1},P_{n-k+2},...,P_{n},P_1,P_2,...,P_{n-k}\}$，也就是對 $X$ 做 $(n-k)$-切牌。

這樣一來，只要從最後一個操作開始反推就能得到最初的數列。

時間複雜度 $O(nm)$。

（注意程式碼中我用的陣列 index 是從 0 開始）
```cpp=
#include <bits/stdc++.h>

using namespace std;

int main(){

    int n, m;
    cin >> n >> m;

    vector<int> x(n);
    for(int i = 0; i < n; i++) cin >> x[i];
    vector<int> a(m);
    for(int i = 0; i < m; i++) cin >> a[i];

    for(int i = m - 1; i >= 0; i--){

        vector<int> t(n);
        if(a[i] == 1){
            int now = 0;
            for(int j = 0; j < n; j += 2) t[now++] = x[j];
            for(int j = 1; j < n; j += 2) t[now++] = x[j];
        }
        else if(a[i] == 2){
            int now = 0;
            for(int j = 0; j < n / 2; j++){
                t[now++] = x[j];
                t[now++] = x[n / 2 + j];
            }
        }
        else{
            int now = 0;
            for(int j = n - a[i]; j < n; j++) t[now++] = x[j];
            for(int j = 0; j < n - a[i]; j++) t[now++] = x[j];
        }
        x = t;

    }

    for(int i = 0; i < n; i++){
        if(i != 0) cout << " ";
        cout << x[i];
    }
    cout << "\n";

    return 0;
}
```

## 第三題

### 題目大意

有一個有 $n$ 格的一維表格，每一格從左到右編號為 $0$ 到 $n-1$，有一隻皮卡丘起始位置為 $0$，每次可做下列兩種操作：

1. 往左走 $L$ 格
2. 往右走 $R$ 格

不能走出表格外面。在完成一次操作後，若移動到的格子為 $i$，皮卡丘會瞬間移動到 $S(i)$，$S(i)$ 是 $i$ 的「瞬間移動位置」，$S(i)$ 有可能等於 $i$，此時 $i$ 稱為停留點，每一個格子的瞬間移動位置必定是停留點，因此不會連續瞬間移動，也就是說，對於任何 $0 \leq S(i) < n$，$S(S(i))=S(i)$。（$S(i)$ 有可能超出界外，表示皮卡丘也不能走到 $i$，不然它會瞬移出界）

給 $n$、目標位置 $P$ 和 $L$、$R$，還有 $S(0)$、$S(1)$、……、$S(n-1)$，求需要幾次操作，皮卡丘才會移動到 $P$。

$2 \leq n \leq 10^6$  
$|S(i)| \leq 10^8$  
起點和目標位置必為停留點

### 解法

把這個表格想成一張有向不帶權圖，每個節點表示一個格子，編號同格子編號。由節點 $i$ 畫有向邊往 $S(i-L)$ 和 $S(i+R)$，如果出界就不要那條邊了。這樣就可以算 $0$ 到 $P$ 的最短路徑，不過這張圖不帶權，所以直接 BFS 就好。

時間複雜度 $O(n)$。

```cpp=
#include <bits/stdc++.h>

#define pii pair<int, int>
#define mp make_pair
#define F first
#define S second

using namespace std;

int main(){

    int n, p, l, r;
    cin >> n >> p >> l >> r;

    vector<int> s(n);
    for(int i = 0; i < n; i++) cin >> s[i];

    vector<bool> visit(n);
    queue<pii> q;
    q.push(mp(0, 0));
    visit[0] = true;
    while(!q.empty()){
        int v = q.front().F, step = q.front().S;
        q.pop();
        if(v == p){
            cout << step << "\n";
            return 0;
        }

        if(v - l >= 0 && s[v - l] >= 0 && s[v - l] < n && !visit[s[v - l]]){
            q.push(mp(s[v - l], step + 1));
            visit[s[v - l]] = true;
        }
        if(v + r >= 0 && s[v + r] >= 0 && s[v + r] < n && !visit[s[v + r]]){
            q.push(mp(s[v + r], step + 1));
            visit[s[v + r]] = true;
        }
    }
    cout << "-1\n";

    return 0;
}
```


## 第四題

### 題目大意

給一個 $m$ 列 $n$ 行（直行橫列）的矩陣，這個矩陣裡的數字只有 $0$ 或 $1$，最上和最下的列與最左和最右的行稱為邊界線，如果一條邊界線上的所有數字都一樣，那這條邊界線可以被刪除，而這個矩陣的行數或列數就會減 1。你可以改變一些數字，使得這個矩陣可以被完全刪除。

給 $m$、$n$ 和原始矩陣，求最少需要改變幾個數字。

$m, n \leq 25$

### 解法

這是 DP，先設第 $r$ 列和第 $c$ 行的交點為 $(r,c)$，令 $dp[x_1][y_1][x_2][y_2]$ 為以 $(x_1, y_1)$ 和 $(x_2, y_2)$ 為左上角和右下角的子矩陣，需要改幾個數字才能被刪除、令 $row(r, y_1, y_2)$ 為 $(r, y_1)$ 到 $(r, y_2)$ 需要改幾個數字才能使所有數字一樣、$column(c, x_1, x_2)$ 為 $(x_1, c)$ 到 $(x_2, c)$ 需要改幾個數字才能使所有數字一樣。

初始狀態：
$dp[i][j][i][j]=0$，也就是大小只有 $1 \times 1$ 的矩陣不需要改任何數字就可以被刪除。

轉移：  
$dp[x_1][y_1][x_2][y_2]=\\
\min(dp[x_1+1][y_1][x_2][y_2]+row(x_1, y_1, y_2),\\
dp[x_1][y_1+1][x_2][y_2]+column(y_1, x_1, x_2), \\
dp[x_1][y_1][x_2-1][y_2]+row(x_2, y_1, y_2), \\
dp[x_1][y_1][x_2][y_2-1]+column(y_2, x_1, x_2))$

時間複雜度 $O(n^2m^2)$。

```cpp=
#include <bits/stdc++.h>

using namespace std;

vector<vector<int>> rs, cs;

int row(int r, int y1, int y2){
    return min(rs[r][y2] - rs[r][y1 - 1], y2 - y1 + 1 - (rs[r][y2] - rs[r][y1 - 1]));
}

int column(int c, int x1, int x2){
    return min(cs[c][x2] - cs[c][x1 - 1], x2 - x1 + 1 - (cs[c][x2] - cs[c][x1 - 1]));
}

int main(){

    int m, n;
    cin >> m >> n;

    vector<vector<int>> a(m + 1, vector<int>(n + 1));
    for(int i = 1; i <= m; i++){
        for(int j = 1; j <= n; j++){
            cin >> a[i][j];
        }
    }

    rs.resize(m + 1, vector<int>(n + 1));
    cs.resize(n + 1, vector<int>(m + 1));

    for(int i = 1; i <= m; i++){
        for(int j = 1; j <= n; j++){
            rs[i][j] = a[i][j] + rs[i][j - 1];
        }
    }

    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            cs[i][j] = a[j][i] + cs[i][j - 1];
        }
    }

    vector<vector<vector<vector<int>>>> dp(m + 1, vector<vector<vector<int>>>(n + 1, vector<vector<int>>(m + 1, vector<int>(n + 1))));

    for(int rl = 1; rl < m; rl++){ //含有 rl+1 列
        for(int cl = 1; cl < n; cl++){ //含有 cl+1 行
            for(int i = 1; i + rl <= m; i++){
                for(int j = 1; j + cl <= n; j++){

                    dp[i][j][i + rl][j + cl] = min({
                        dp[i + 1][j][i + rl][j + cl] + row(i, j, j + cl),
                        dp[i][j + 1][i + rl][j + cl] + column(j, i, i + rl),
                        dp[i][j][i + rl - 1][j + cl] + row(i + rl, j, j + cl),
                        dp[i][j][i + rl][j + cl - 1] + column(j + cl, i, i + rl)
                    });
                }
            }
        }
    }

    cout << dp[1][1][m][n] << "\n";

    return 0;
}
```