# APCS 2023/1月 題解
## ==程式考試==
### <font color="0E81A6">敘述</font>
給 n 個提交紀錄,第 i 個提交紀錄有兩個整數
ti 和 si 代表上傳時間和該次上傳的分數,若第 i 次的提交結果為嚴重錯誤,則 si 為 -1。
計算總分的公式為 提交紀錄中的最高分 - 總提交次數 - 總嚴重錯誤次數 * 2,若計算出來的分數為負數則計為 0 。
請輸出總分和第一次獲得最高分的時間點。
### <font color="0E81A6">輸入說明</font>
第一行有一個正整數 K (1<=K<=6) 代表提交的次數,接下來有 K 行,第
i 行有兩個整數 ti 和 si (1<=ti,si<=100) 。保證提交紀錄按照時間點嚴格遞增排序,並且第一個提交紀錄不會是嚴重錯誤。
### <font color="0E81A6">輸出說明</font>
輸出兩個整數,代表總分和第一次獲得最高分的時間點。
### <font color="E3A506">核心</font>
條件判斷
### <font color="E3A506">思路</font>
很簡單,只是變數會設比較多。
```c++=
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
scanf("%d",&n);
int time,score;
int maxScoreTime=0,maxScore=-2,badtime=0;
for(int i=0;i<n;i++)
{
scanf("%d%d",&time,&score);
if(score>maxScore) maxScoreTime=time,maxScore=score;
if(score==-1) badtime++;
}
maxScore-=(n+badtime*2);
maxScore=max(0,maxScore);
printf("%d %d\n",maxScore,maxScoreTime);
}
```
## ==造字程式==
### <font color="0E81A6">敘述</font>
有一個長度 K 的初始字串 S ,每個字元都是小寫的英文字母。
接下來有 Q 次的修改,每次的修改會把舊的字串重新排列成一個新的字串。
更具體來講,每次修改時會給一個 1 ~ K 的排列 P=[P1,P2,...,Pk] ,要將舊字串的第 i 的字元複製到新字串的第 Pi 個字元。
在 Q 的修改中,每次修改出來的新字串會被當成下一次修改中的舊字串,而第一次修改時使用的舊字串就是初始字串。
題目另外會給一個數字 R ,請依照下面定義的順序輸出 R 行,每行 Q 個字元
- 輸出操作 1 ~ Q 的新字串的第 1 個字元
- 輸出操作 1 ~ Q 的新字串的第 2 個字元
- ...
- 輸出操作 1 ~ Q 的新字串的第 Q 個字元
### <font color="0E81A6">輸入說明</font>
第一行有三個整數 K,Q,R
第二行是長度 K 的初始字串
接下來有 Q 行,每行有是一個 1 ~ K 的排列
### <font color="0E81A6">輸出說明</font>
請依照題目敘述輸出 RxQ 個字元
### <font color="E3A506">核心</font>
二維陣列、模擬
### <font color="E3A506">思路</font>
設二維陣列,列向下模擬。
```c++=
#include<bits/stdc++.h>
using namespace std;
int main()
{
int K,Q,R;
char str[25][25];
scanf("%d%d%d%s",&K,&Q,&R,str[0]);
int locate;
for(int i=1;i<=Q;i++)
{
for(int j=0;j<K;j++)
{
scanf("%d",&locate);
str[i][locate-1]=str[i-1][j];
}
}
for(int j=0;j<R;j++)
{
for(int i=1;i<=Q;i++) printf("%c",str[i][j]);
printf("\n");
}
}
```
## ==先加後乘與函數==
### <font color="0E81A6">敘述</font>
給一個運算式,運算式的內容由數字、+、* 和某個函式 f() 所組成,除了函式以外不會有額外的括號。請將此運算式依照 先加後乘 的方式運算。
函式 f(x1,x2,x3,...) 定義為從這個不定長度的參數中的最大值扣掉最小值。
### <font color="0E81A6">輸入說明</font>
輸入一個運算式,保證長度不超過 500 ,出現在運算式內的數字介於 0 到 200 之間,除了函式 f() 之外不會出現多餘的括號,並且運算式一定合法。
### <font color="0E81A6">輸出說明</font>
輸出運算式的計算結果,此題運算過程和答案可能超過 2^31^ 但不超過 10^17^。
### <font color="E3A506">核心</font>
遞迴
### <font color="E3A506">思路</font>
這題我卡很久,困在f嵌套的處理以及運算的不當,最後參考別的大佬的解法才頓悟。
[大神解法](https://hackmd.io/@QWERTYPIG/SJG4_ztoo#APCS-2023%E5%B9%B401%E6%9C%88%E9%A1%8C%E8%A7%A3)
## ==機器出租==
### <font color="0E81A6">敘述</font>
有 N 個活動,舉辦每個活動都要租借一台機器,若要舉辦第 i 個活動,就需要在時間段 [Li,Ri] 租借用一台機器。
已知 N 個活動的需要使用的時間段,並且有 K 台機器可以開放租借,且一台機器同時間只能借給一個活動,請問最多可以成功舉辦多少場活動?
### <font color="0E81A6">輸入說明</font>
第一行有兩個正整數 N 和 K
第二行有 N 個正整數,第 i 個正整數是活動 i 的開始時間
第三行有 N 個正整數,第 i 個正整數是活動 i 的結束時間
### <font color="0E81A6">輸出說明</font>
輸出一個整數表示最多可以舉辦多少活動。
### <font color="E3A506">核心</font>
貪心法
### <font color="E3A506">思路</font>
要如何在O(n)時間內,資料從頭掃到尾解決?
先用struct記錄活動開始時間和結束時間,接著建一個陣列。
貪心嘛,故我希望結束時間越快越好,機器才能給下一場活動使用,所以我對結束時間大小進行排序(需用到比較函數)。
若K=1:
endtime記錄結束時間,若下一場活動開始時間<endtime,則很高興,我可以租借給此活動,再把endtime更新為下場活動的結束時間。以此概念走訪一次陣列,答案必定包含在最佳解內。
若K=N
以set記錄目前租借活動的結束時間,每次取最快結束時間出來做比較,若下一場開始時間小於此結束時間,則K不動,機器輪替就好,若大於的話,則租借一台新的機器給它。
很直觀,狀態會一直保持所有機器都租出去,機器不可能回來了哈哈哈。
```c++=
#include<bits/stdc++.h>
using namespace std;
#define N 100005
struct Time
{
int start,finish;
};
bool cmp(Time x, Time y)
{
return x.finish<y.finish;
}
int main()
{
int n,k;
Time activity[N];
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++) scanf("%d",&activity[i].start);
for(int i=0;i<n;i++) scanf("%d",&activity[i].finish);
sort(activity,activity+n,cmp);
multiset<int> endtime;
endtime.insert(-1),k--;
int num=0;
for(int i=0;i<n;i++)
{
auto it=endtime.lower_bound(activity[i].start);
if(it!=endtime.begin())
{
it--;
num++;
endtime.erase(it);
endtime.insert(activity[i].finish);
}
else if(k>0)
{
k--;
num++;
endtime.insert(activity[i].finish);
}
}
printf("%d\n",num);
}
```