---
# System prepended metadata

title: APCS 2021年01月題解
tags: [APCS]

---

# APCS 2021年01月題解
## 第一題 購買力
### 題目敘述
https://zerojudge.tw/ShowProblem?problemid=f605
### 解套
給n組數字，每組三個數字分別為a,b,c，求有幾組最大值減最小值大於等於一數字k
以及他們平均數的總和
### 題目分析
開兩個變數sum與sum2紀錄購買數量與總花費，接著直接吃輸入，用max與min函式得到最大值與最小值，若大於k就更新sum與sum2，最後輸出
### 解題流程
輸入$→$判斷$→$輸出
### 解題過程
#### 變數宣告
```cpp=3
int n,d,a,b,c,sum=0,sum2=0,mx,mn;
```
#### 輸入
```cpp=5
cin>>n>>d;
while(n--){
    cin>>a>>b>>c;
```
#### 條件判斷、更新答案
```cpp=8
    mx=max({a,b,c});
    mn=min({a,b,c});
    if(mx-mn>=d){
        sum++;
        sum2+=(a+b+c)/3;
    }
```
其中max({a,b,c})等價於max(a,max(b,c))
#### 輸出
```cpp=15
cout<<sum<<" "<<sum2;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int n,d,a,b,c,sum=0,sum2=0,mx,mn;
int main(){
    cin>>n>>d;
    while(n--){
    	cin>>a>>b>>c;
    	mx=max({a,b,c});
    	mn=min({a,b,c});
    	if(mx-mn>=d){
    		sum++;
    		sum2+=(a+b+c)/3;
    	}
    }
    cout<<sum<<" "<<sum2;
}
```
## 第二題 流量
### 題目敘述 
https://zerojudge.tw/ShowProblem?problemid=f606
### 解套
N/A
### 題目分析
直接按題目要求處理即可
因為第$i$個伺服器要傳到城市$j$的流量(即$Q[i][j]$)是固定的，所以先存著
而對每一個方案會有不同配置方式，所以對每一個方案計算需要的總花費，再取最小值即可

實作上我寫了一個函式$calc$計算每個方案的花費，先用一維陣列$server$紀錄每個伺服器在哪個城市，
再開一個二維陣列$city$，其中$city[i][j]$紀錄第$i$個城市要傳到第$j$個城市的總流量
因為$i$個伺服器會位在$server[i]$這個城市並對第$j$個城市有$Q[i][j]$的流量，
故遍歷所有可能，並讓$city[server[i]][j]$加上$Q[i][j]$就能得到所有城市間的流量傳送
最後依照題目的花費計算原則計算花費即可
最後複雜度$O(mnk)$
### 解題流程
輸入$→$遍歷、用calc函式計算、更新答案$→$輸出
### 解題過程
#### 變數宣告
```cpp=3
int n,m,k,q[50+10][50+10],server[50+10],city[50+10][50+10];
```
名稱與題述、前述相同($Q$改為$q$)
```cpp=22
int ans=INT_MAX;
```
ans預設為極大值
#### $calc$函式
```cpp=4
int calc(){
    for(int i=0;i<m;i++)for(int j=0;j<m;j++)city[i][j]=0;
    for(int i=0;i<n;i++)for(int j=0;j<m;j++){
        city[server[i]][j]+=q[i][j];
    }
    int ans=0;
    for(int i=0;i<m;i++)for(int j=0;j<m;j++){
        if(i==j)ans+=city[i][j];
        else{
            if(city[i][j]<=1000)ans+=city[i][j]*3;
            else ans+=3000+(city[i][j]-1000)*2;
        }
    }
    return ans;
}
```
要記得讓$city$歸零(第5行)
花費計算依照題目敘述
1. 在同一個城市每單位流量1元(第11行)
2. 不同城市小於等於1000每單位流量3元(第13行)
3. 不同城市大於1000的部分每單位流量2元(第14行)

#### 輸入
```cpp=20
cin>>n>>m>>k;
for(int i=0;i<n;i++)for(int j=0;j<m;j++)cin>>q[i][j];
```
#### 處理各方案
```cpp=23
while(k--){
    for(int i=0;i<n;i++)cin>>server[i];
    ans=min(ans,calc());
}
```
#### 輸出
```cpp=27
cout<<ans;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int n,m,k,q[50+10][50+10],server[50+10],city[50+10][50+10];
int calc(){
    for(int i=0;i<m;i++)for(int j=0;j<m;j++)city[i][j]=0;
    for(int i=0;i<n;i++)for(int j=0;j<m;j++){
        city[server[i]][j]+=q[i][j];
    }
    int ans=0;
    for(int i=0;i<m;i++)for(int j=0;j<m;j++){
        if(i==j)ans+=city[i][j];
        else{
            if(city[i][j]<=1000)ans+=city[i][j]*3;
            else ans+=3000+(city[i][j]-1000)*2;
        }
    }
    return ans;
}
int main(){
    cin>>n>>m>>k;
    for(int i=0;i<n;i++)for(int j=0;j<m;j++)cin>>q[i][j];
    int ans=INT_MAX;
    while(k--){
        for(int i=0;i<n;i++)cin>>server[i];
        ans=min(ans,calc());
    }
    cout<<ans;
}
```
## 第三題 切割費用
### 題目敘述
https://zerojudge.tw/ShowProblem?problemid=f607
### 解套
對一動態且由小到大排的陣列插入數字，每次花費是所插入數字後一個減前一個，求總花費
喔然後插入數字沒有照順序給，要自己處理
### 題目分析
首先要處理操作沒有照順序給的問題
一開始可能會想要對每個$\{x,i\}$按$i$排序之類的，但不用那麼麻煩
因為題目有說$n$最大只到$200000$，所以可以直接開陣列紀錄就好(用排的也會過，只是稍微慢一些。一開始我沒想太多也是用排的，後來才發現可以更快，~~對以前智障的自己感到傻眼~~)

接著是如何快速地得到答案
如果用vector然後每次遍歷找前後兩個數字複雜度會是$O(n^2)$，明顯超時
因此我們用c++內建的資料結構set來處理這個問題
每次先插入切的位置後，利用find與$++、--$找到前一個與後一個的指標，
取值後就能得到切的長度，全部加起來就是答案

時間複雜度$O(nlogn)$
### 解題流程
輸入$→$模擬切割過程$→$輸出
### 解題過程
#### 變數宣告
```cpp=3
int n,l,cut[200000+10],a,b;
long long int ans=0;
set<int>st;
```
n,l同題述，cut紀錄每個時間點切的地方，ans存答案且要記得開long long
#### 輸入
```cpp=7
cin>>n>>l;
for(int i=1;i<=n;i++){
    cin>>a>>b;
    cut[b]=a;
}
```
#### 切割模擬
```cpp=12
st.insert(0);
st.insert(l);
for(int i=1;i<=n;i++){
    st.insert(cut[i]);
    auto pre=st.find(cut[i]),nxt=st.find(cut[i]);
    pre--;
    nxt++;
    ans+=*nxt-*pre;
}
```
一開始(第12、13行)先在set裡insert頭與尾，方便後續操作
接下來依序處理所有切割點，先把位置插入set裡後利用find找到位置，再用$++$和$--$找到前一個和後一個切割點的位置(第15-18行)，並把兩者取值後相減就是所切斷的長度(第19行)
#### 輸出
```cpp=21
cout<<ans;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int n,l,cut[200000+10],a,b;
long long int ans=0;
set<int>st;
int main(){
    cin>>n>>l;
    for(int i=1;i<=n;i++){
        cin>>a>>b;
        cut[b]=a;
    }
    st.insert(0);
    st.insert(l);
    for(int i=1;i<=n;i++){
        st.insert(cut[i]);
        auto pre=st.find(cut[i]),nxt=st.find(cut[i]);
        pre--;
        nxt++;
        ans+=*nxt-*pre;
    }
    cout<<ans;
}
```
## 第四題 飛黃騰達
### 題目敘述
https://zerojudge.tw/ShowProblem?problemid=f608
### 解套
給定一些二維座標，求按某座標排序後，另一座標組成數列的最長遞增子序列(說明在後面)
### 題目分析
因為飛黃只能往右上方移動，所以其移動軌跡的x座標與y座標必定都是不嚴格遞增的，
因此我們如果按照其中一個座標由小到大排序，那麼對該座標而言行進順序一定是合法的
(如$[\{1,1\},\{2,5\},\{3,2\}]$按y座標排序後變成$[\{1,1\},\{3,2\},\{2,5\}]$)只看y座標確實是一直往上走
這時會發現既然某個方向的移動是全部合法的，那麼只要另一個方向的座標也都合法(不嚴格遞增)，就能找到一組合法的走法，而其中最長的就是我們要的答案

於是得到我們要的解法：將各座標按某方向排序後(我是用x)，求另一個方向的最長遞增子序列(LIS)
(LIS的做法說明在[這裡](https://web.ntnu.edu.tw/~algo/Subsequence.html#3))
唯一需要注意的是因為這題只需要非嚴格遞增就好($\{2,3\}$可以走到$\{3,3\}$)，所以lower_bound要改成upper_bound

最後時間複雜度$O(nlogn)$
### 解題流程
輸入$→$排序$→$找LIS$→$輸出
### 解題過程
#### define
```cpp=2
#define F first
#define S second
```
~~因為我懶得打6個字母~~
#### 變數宣告
```cpp=5
int n;
vector<pair<int,int>>v;
```
v存各座標
#### 輸入
```cpp=8
cin>>n;
v.assign(n,{});
for(int i=0;i<n;i++)cin>>v[i].F>>v[i].S;
```
第9行初始化v
#### 排序
```cpp=11
sort(v.begin(),v.end());
```
按x座標由小到大排
#### 找最長非嚴格遞增子序列
```cpp=13
for(auto i:v){
    if(tmp.empty()||tmp.back()<=i.S)tmp.push_back(i.S);
    else tmp[upper_bound(tmp.begin(),tmp.end(),i.S)-tmp.begin()]=i.S;
}
```
#### 輸出
```cpp=17
cout<<tmp.size();
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
#define F first
#define S second
using namespace std;
int n;
vector<pair<int,int>>v;
int main(){
    cin>>n;
    v.assign(n,{});
    for(int i=0;i<n;i++)cin>>v[i].F>>v[i].S;
    sort(v.begin(),v.end());
    vector<int>tmp;
    for(auto i:v){
        if(tmp.empty()||tmp.back()<=i.S)tmp.push_back(i.S);
        else tmp[upper_bound(tmp.begin(),tmp.end(),i.S)-tmp.begin()]=i.S;
    }
    cout<<tmp.size();
}
```