# 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();
}
```