---
# System prepended metadata

title: APCS 2020年10月題解
tags: [APCS]

---

# APCS 2020年10月題解
## 第一題 人力分配
### 題目敘述
https://zerojudge.tw/ShowProblem?problemid=f312
### 解套
N/A
### 題目分析
直接嘗試每一種組合，找到最大可能收益

時間複雜度$O(n)$
### 解題流程
輸入$→$枚舉$→$輸出
### 解題過程
#### 變數宣告
```cpp=3
int a1,b1,c1,a2,b2,c2,n;
```
變數名與題述相同
```cpp=6
int ans=INT_MIN;
```
一開始設答案為極小值
#### 輸入
```cpp=5
cin>>a1>>b1>>c1>>a2>>b2>>c2>>n;
```
#### 暴力枚舉
```cpp=7
for(int x1=0;x1<=n;x1++){
    int x2=n-x1;
    ans=max(ans,a1*x1*x1+b1*x1+c1+a2*x2*x2+b2*x2+c2);
}
```
枚舉所有x1與x2的可能，並更新答案
#### 輸出
```cpp=11
cout<<ans;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int a1,b1,c1,a2,b2,c2,n;
int main(){
    cin>>a1>>b1>>c1>>a2>>b2>>c2>>n;
    int ans=INT_MIN;
    for(int x1=0;x1<=n;x1++){
    	int x2=n-x1;
    	ans=max(ans,a1*x1*x1+b1*x1+c1+a2*x2*x2+b2*x2+c2);
    }
    cout<<ans;
}
```
## 第二題 人口遷移
### 題目敘述 
https://zerojudge.tw/ShowProblem?problemid=f313
### 解套
N/A
### 題目分析
直接按照題目說的規則處理即可，不過需要注意的是因為遷移是同步的，但我們的處理只能依序做，
所以遷移到其他地區的量先開另一個陣列math紀錄，所有地區都處理完後再加上去
(如果直接把遷移的量加上去會造成後處理的地區出錯)
另外因為題目保證$k\geq4$，所以不論如何人數都不會是負的，不用特別處理

處理遷移$m$次後再遍歷一次找最小值與最大值

時間複雜度$O(mrc)$
### 解題流程
輸入$→$模擬$→$遍歷$→$輸出
### 解題過程
#### 變數宣告
```cpp=3
int r,c,k,m;
int arr[50][50],math[50][50];
```
r,c,k,m與題述相同，arr存總人數，math存每次遷移暫時增加的人數
```cpp=42
int minans=INT_MAX,maxans=0;
```
初始化最小值與最大值
#### 輸入
```cpp=6
cin>>r>>c>>k>>m;
for(int i=0;i<r;i++)for(int j=0;j<c;j++)cin>>arr[i][j];
```
#### 處理遷移m次
```cpp=8
for(int l=0;l<m;l++){
    for(int i=0;i<r;i++)for(int j=0;j<c;j++)math[i][j]=0;
    for(int i=0;i<r;i++)for(int j=0;j<c;j++){
    	if(arr[i][j]==-1)continue;
        int num=arr[i][j]/k;
        int cnt=0;
        if(i!=0){
            if(arr[i-1][j]!=-1){
                math[i-1][j]+=num;
                cnt++;
            }
        }
        if(j!=0){
            if(arr[i][j-1]!=-1){
                    math[i][j-1]+=num;
                cnt++;
            }
        }
        if(i!=r-1){
            if(arr[i+1][j]!=-1){
                math[i+1][j]+=num;
                cnt++;
            }
        }
        if(j!=c-1){
            if(arr[i][j+1]!=-1){
                math[i][j+1]+=num;
                cnt++;
            }
        }
        arr[i][j]-=num*cnt;
    }
    for(int i=0;i<r;i++)for(int j=0;j<c;j++)if(arr[i][j]!=-1)arr[i][j]+=math[i][j];
}
```
一開始(第9行)先歸零math

接著遍歷所有位置(第10-39行)
如果該位置不是城市(值是$-1$)就跳過(第11行)
不然開兩個變數num與cnt紀錄要遷移的人數與共要遷移到幾個相鄰區域(第12、13行)
接著看上下左右四個相鄰區域是不是城市，如果是就把人移過去(math加num，cnt加1)(第14-37行)
四個都處理完後一次性減掉人數(第38行)

每次處理完所有位置再用math更新arr(第40行)
#### 找答案
```cpp=43
for(int i=0;i<r;i++)for(int j=0;j<c;j++){
	if(arr[i][j]!=-1)minans=min(minans,arr[i][j]);
	maxans=max(maxans,arr[i][j]);
}
```
遍歷所有區域更新答案
其中因為代表不是都市的-1會影響取最小值的操作，所以要額外判斷(第44行)
當然也可以一開始遇到-1就直接continue~~只是這樣會多一行~~
#### 輸出
```cpp=47
cout<<minans<<"\n"<<maxans;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int r,c,k,m;
int arr[50][50],math[50][50];
int main(){
    cin>>r>>c>>k>>m;
    for(int i=0;i<r;i++)for(int j=0;j<c;j++)cin>>arr[i][j];
    for(int l=0;l<m;l++){
    	for(int i=0;i<r;i++)for(int j=0;j<c;j++)math[i][j]=0;
    	for(int i=0;i<r;i++)for(int j=0;j<c;j++){
    	    if(arr[i][j]==-1)continue;
            int num=arr[i][j]/k;
            int cnt=0;
            if(i!=0){
                if(arr[i-1][j]!=-1){
                    math[i-1][j]+=num;
                    cnt++;
                }
            }
            if(j!=0){
                if(arr[i][j-1]!=-1){
                    math[i][j-1]+=num;
                    cnt++;
                }
            }
            if(i!=r-1){
                if(arr[i+1][j]!=-1){
                    math[i+1][j]+=num;
                    cnt++;
                }
            }
            if(j!=c-1){
                if(arr[i][j+1]!=-1){
                    math[i][j+1]+=num;
                    cnt++;
                }
            }
            arr[i][j]-=num*cnt;
        }
        for(int i=0;i<r;i++)for(int j=0;j<c;j++)if(arr[i][j]!=-1)arr[i][j]+=math[i][j];
    }
    int minans=INT_MAX,maxans=0;
    for(int i=0;i<r;i++)for(int j=0;j<c;j++){
    	if(arr[i][j]!=-1)minans=min(minans,arr[i][j]);
    	maxans=max(maxans,arr[i][j]);
    }
    cout<<minans<<"\n"<<maxans;
}
```
## 第三題 勇者修煉
### 題目敘述
https://zerojudge.tw/ShowProblem?problemid=f314
### 解套
N/A
### 題目分析
(為方便討論，設$arr[i][j]$為原本的值，$dp[i][j]$為考慮到第$i$行$j$列的情形)
(如果沒有dp基礎可以先去看[這裡](https://oi-wiki.org/dp/))

先把題目~~大幅~~簡化一下
假設從左上角開始，而且只能往右下方走，那要怎麼寫？
那就是常見的dp題目嘛，轉移式$dp[i][j]=max(dp[i-1][j],dp[i][j-1])+arr[i][j]$

那如果加上可從最上層任意位置開始要怎麼做？
只要在處理最上層時增加只考慮自己的可能就好了 $dp[0][j]=max(dp[0][j-1],0)+arr[0][j]$

最後處理完整的題目，即加上左右都能走的條件
因為由右往左跟由左往右可能會有不一樣的結果，取用上也不能互相通用
| 1 | 2 | 3 | 4 |
| - | - | - | - |
| 5 | 6 | 7 | 8 |

(假設要從7走到6，因為不能重複走所以只能考慮3→7→6和8→7→6而不能考慮6→7→6)
所以往右跟往左的dp要分開存

因此我們把dp的定義改成$dp[i][j][k]$，$k=0$代表從左邊走過來，$k=1$代表從右邊走過來
dp轉移式就變成$dp[i][j][0]=max({dp[i][j-1][0],dp[i-1][j][0],dp[i-1][j][1]})+arr[i][j]$
以及$dp[i][j][1]=max({dp[i][j+1][1],dp[i-1][j][0],dp[i-1][j][1]})+arr[i][j]$

初始化的部分則是$dp[0][0][0]=arr[0][0],dp[0][n-1][1]=arr[0][n-1]$，
最上面一列取max時不用考慮上面但要加上直接從自己開始的可能性，
且每一列最左和最右會缺少從右邊來和從左邊來的可能性

dp跑完後再掃最後一列，看最大值是多少並輸出

時間複雜度$O(mn)$
### 解題流程
輸入$→$動態規劃$→$遍歷$→$輸出
### 解題過程
#### 變數宣告
```cpp=3
int m,n,arr[50][10000],dp[50][10000][2]={};
```
變數名與前述、題述相同
```cpp=21
int ans=INT_MIN;
```
一開始設答案為極小值
#### 輸入
```cpp=5
cin>>m>>n;
for(int i=0;i<m;i++)for(int j=0;j<n;j++)cin>>arr[i][j];
```
#### 動態規劃
```cpp=7
dp[0][0][0]=arr[0][0];
dp[0][n-1][1]=arr[0][n-1];
for(int i=0;i<m;i++){
    if(i==0){
        for(int j=1;j<n;j++)dp[i][j][0]=max(dp[i][j-1][0],0)+arr[i][j];
        for(int j=n-2;j>=0;j--)dp[i][j][1]=max(dp[i][j+1][1],0)+arr[i][j];
    }
    else{
        dp[i][0][0]=max(dp[i-1][0][0],dp[i-1][0][1])+arr[i][0];
        dp[i][n-1][1]=max(dp[i-1][n-1][1],dp[i-1][n-1][0])+arr[i][n-1];
        for(int j=1;j<n;j++)dp[i][j][0]=max({dp[i][j-1][0],dp[i-1][j][0],dp[i-1][j][1]})+arr[i][j];
        for(int j=n-2;j>=0;j--)dp[i][j][1]=max({dp[i][j+1][1],dp[i-1][j][0],dp[i-1][j][1]})+arr[i][j];
    }
}
```
第7、8行初始化左上角與右上角
第10-13行初始化最上面一列
第15、16行計算每一列最左與最右一格
第17、18行計算剩下的格子
#### 找答案
```cpp=21
int ans=INT_MIN;
for(int i=0;i<n;i++)ans=max({ans,dp[m-1][i][0],dp[m-1][i][1]});
```
遍歷最後一列並更新答案
#### 輸出
```cpp=23
cout<<ans;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int m,n,arr[50][10000],dp[50][10000][2]={};
int main(){
    cin>>m>>n;
    for(int i=0;i<m;i++)for(int j=0;j<n;j++)cin>>arr[i][j];
    dp[0][0][0]=arr[0][0];
    dp[0][n-1][1]=arr[0][n-1];
    for(int i=0;i<m;i++){
        if(i==0){
            for(int j=1;j<n;j++)dp[i][j][0]=max(dp[i][j-1][0],0)+arr[i][j];
            for(int j=n-2;j>=0;j--)dp[i][j][1]=max(dp[i][j+1][1],0)+arr[i][j];
        }
        else{
            dp[i][0][0]=max(dp[i-1][0][0],dp[i-1][0][1])+arr[i][0];
            dp[i][n-1][1]=max(dp[i-1][n-1][1],dp[i-1][n-1][0])+arr[i][n-1];
            for(int j=1;j<n;j++)dp[i][j][0]=max({dp[i][j-1][0],dp[i-1][j][0],dp[i-1][j][1]})+arr[i][j];
            for(int j=n-2;j>=0;j--)dp[i][j][1]=max({dp[i][j+1][1],dp[i-1][j][0],dp[i-1][j][1]})+arr[i][j];
        }
    }
    int ans=INT_MIN;
    for(int i=0;i<n;i++)ans=max({ans,dp[m-1][i][0],dp[m-1][i][1]});
    cout<<ans;
}
```
## 第四題 低地距離
### 題目敘述
https://zerojudge.tw/ShowProblem?problemid=f315
### 解套
題目寫得好清楚喔怎麼辦
定義$i$的低窪值為一陣列裡兩個數值為$i$的位置中間有幾個小於$i$的是數字，求所有低窪值總和
### 題目分析
首先這題如果暴力做的話時間複雜度會是$O(n^2)$，要執行$10^{10}$次運算，會超時
那究竟要怎麼做呢？

因為題目要的是兩相同數值間比他們小的，而因為從小到大具有規律性，
所以我們可以把題目稍微做一下轉換

如果我們把數字從小到大掃，並開一個新的陣列，每掃到一個數字就在他出現的兩個位置標記，就能動態紀錄比某數字小的數字出現位置
以題目的$[3,1,2,1,3,2]$為例
1. 掃到1

| 位置 | 1 | 2 | 3 | 4 | 5 | 6 |
| --  | - | - | - | - | - | - |
| 標記 | 0 | 1 | 0 | 1 | 0 | 0 |
2. 掃到2

| 位置 | 1 | 2 | 3 | 4 | 5 | 6 |
| --  | - | - | - | - | - | - |
| 標記 | 0 | 1 | 1 | 1 | 0 | 1 |
3. 掃到3

| 位置 | 1 | 2 | 3 | 4 | 5 | 6 |
| --  | - | - | - | - | - | - |
| 標記 | 1 | 1 | 1 | 1 | 1 | 1 |

就會發現一個數字的低窪值就是掃到他之前，他兩個位置之間的區間和！
例如數值3的位置是$1,5$，而掃完2後$[1,5]$的區間和是3，也就是數值3的低窪值

但是要怎麼做到動態更新數值與求區間和呢？答案是用**BIT**(樹狀數組)
他支持在$O(n)$時間初始化，$O(logn)$時間單點加值與求前綴和
BIT的詳細介紹在[這裡](https://oi-wiki.org/ds/fenwick/)(也可以用線段樹，但BIT寫起來比較簡潔)

於是就得到解法：先掃過一遍原始陣列得到各數值的出現位置，接著數字從小到大處理，先把答案加上出現位置之間的區間和再用出現位置更新BIT，最後輸出答案即可

時間複雜度$O(nlogn)$
### 解題流程
輸入$→$用BIT更新答案、操作區間$→$輸出
### 解題過程
#### define
```cpp=2
#define F first
#define S second
```
懶得打字
#### 變數宣告
```cpp=5
int n,a;
long long int ans=0;
pair<int,int>arr[100000+10];
```
a是輸入用的變數，ans是答案(題目說要long long)，arr存個數值出現位置
#### BIT
```cpp=8
struct BIT{
    int sz;
    vector<int>dat;
    void init(int n){
        sz=n;
        dat.assign(n+1,0);
        return;
    }
    void upd(int id,int x){
        for(int i=id;i<=sz;i+=i&-i)dat[i]+=x;
        return;
    }
    int sum(int id){
        int ans=0;
        for(int i=id;i>0;i-=i&-i)ans+=dat[i];
        return ans;
    }
}bit;
```
#### 輸入、存位置
```cpp=27
cin>>n;
for(int i=1;i<=2*n;i++){
    cin>>a;
    if(arr[a].F==0)arr[a].F=i;
    else arr[a].S=i;
}
```
第30、31行存數值出現的第一和第二個位置
#### 初始化BIT
```cpp=33
bit.init(2*n);
```
要記得大小是$2n$
#### 操作
```cpp=34
for(int i=1;i<=n;i++){
    ans+=bit.sum(arr[i].S-1)-bit.sum(arr[i].F);
    bit.upd(arr[i].F,1);
    bit.upd(arr[i].S,1);
}
```
更新答案、BIT
#### 輸出
```cpp=39
cout<<ans;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
#define F first
#define S second
using namespace std;
int n,a;
long long int ans=0;
pair<int,int>arr[100000+10];
struct BIT{
    int sz;
    vector<int>dat;
    void init(int n){
        sz=n;
        dat.assign(n+1,0);
        return;
    }
    void upd(int id,int x){
        for(int i=id;i<=sz;i+=i&-i)dat[i]+=x;
        return;
    }
    int sum(int id){
        int ans=0;
        for(int i=id;i>0;i-=i&-i)ans+=dat[i];
        return ans;
    }
}bit;
int main(){
    cin>>n;
    for(int i=1;i<=2*n;i++){
        cin>>a;
        if(arr[a].F==0)arr[a].F=i;
        else arr[a].S=i;
    }
    bit.init(2*n);
    for(int i=1;i<=n;i++){
        ans+=bit.sum(arr[i].S-1)-bit.sum(arr[i].F);
        bit.upd(arr[i].F,1);
        bit.upd(arr[i].S,1);
    }
    cout<<ans;
}
```