歡迎加入我的 Discord 群組與我討論程式相關的問題!

Posted on 

 by 

 in 

2023 年 1 月 APCS 題解

1. 程式考試

用迴圈跑過所有的提交紀錄即可,利用變數紀錄最高分數的時間點以及有幾次嚴重錯誤。

複雜度: O(k)

主要知識: 條件判斷與迴路 (conditional expressions and loop)

#include <bits/stdc++.h>
using namespace std;
int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int k;
    cin >> k;
    int wrong = 0, maxScore = -2, maxTime = 0;
    for(int i = 1;i <= k;i++){
        int t, s;
        cin >> t >> s;
        if(s == -1) wrong ++; // 嚴重錯誤
        if(s > maxScore){ // 最高分數
            maxScore = s; // 更新時間,最高分數
            maxTime = t;
        }
    }
    cout << max(0, maxScore - k - wrong * 2) << ' ' << maxTime << '\n';
}

2. 造字程式

利用二維陣列來模擬每次修改後的字串,最後一起輸出。
我這邊是採用 1-base 的寫法,所以輸入一開始的字串後要把每個字元往後移從 0-base 變成 1-base。

複雜度: O(qk)

主要知識: 條件判斷與迴路 (conditional expressions and loop)陣列與結構 (arrays and structures)

#include <bits/stdc++.h>
using namespace std;
char ans[25][25]; // 
int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int k, q, r;
    cin >> k >> q >> r;
    cin >> ans[0]; // ans[i][j] 紀錄第i次修改後 第j個字元是什麼
    for(int i = k;i >= 1;i--){ // 把原本的字串往後移變成 1-base
        ans[0][i] = ans[0][i-1];
    }
    for(int i = 0;i < q;i++){
        for(int j = 1;j <= k;j++){
            int x; cin >> x;
            ans[i+1][x] = ans[i][j];
        }
    }
    for(int i = 1;i <= r;i++){
        for(int j = 1;j <= q;j++){
            cout << ans[j][i];
        }
        cout << '\n';
    }
}

3. 先加後乘與函數

這題有主要幾個難點:

  • 運算處理要先加後乘
  • f 函數需要處理

這邊我們利用遞迴的想法來實作,每次傳入一個現在的字串
每次遇到 f 字串就將 f 字串整個遞迴下去尋找答案
剩下就是數字的處理以及一些實作細節:

  • 把字串轉換成數字時可以使用 C++內建的 stoi 函數
  • 需要紀錄括號數量,以便判斷傳入 f 的參數要在哪裡結束以及 f 在哪裡結束
    • 如果不這樣判斷的話,當 f 函式中有第二個 f 函式就會出現問題,因為不知道哪部分是屬於哪個地方 Ex. f(1, f(1, 2), 3)
  • 在跑過每個字元時,先將加法的部分轉成數字處理好,再跑完整個迴圈時再一起乘,達到先加後乘的效果

複雜度: O(n^2)

主要知識: 函數呼叫與遞迴 (function call and recursion)

#include <bits/stdc++.h>
#define ll long long
#define ft first
#define sc second
using namespace std;
ll f(string s){ // 模擬
    ll cur = 0; // 紀錄
    vector <ll> mul; // 紀錄最後有哪些數字需要乘起來
    string num = ""; // 紀錄現在的數字
    string func = ""; // 紀錄 f 函式以便之後整個遞迴下去算
    int cnt = 0; // 紀錄有幾個括號 (判斷要在什麼時候停下來)
    if(s[0] == 'f'){ // 如果我們現在要處理 f 字串
        ll mn = INT_MAX, mx = 0;
        cnt = 1; // 從第二個開始跑 
        for(int i = 2;i < s.size();i++){  // 所以第一個括號先加上去 (cnt = 1)
            if(s[i] == '(') cnt++; 
            if(s[i] == ')') cnt--; // 遇到左括號 + 1 右括號 - 1
            if(i == s.size() - 1 || (cnt == 1 && s[i] == ',')){ // 在這之前的所有括號都匹配完了
                ll tmp = f(num);
                mn = min(mn, tmp);
                mx = max(mx, tmp);
                num = ""; 
            } else {
                num += s[i]; 
            }
        }
        return mx - mn;
    } else {
        bool found = false;
        for(int i = 0;i <= s.size();i++){
            if(s[i] == 'f' || found){
                func += s[i];
                found = true;
                if(s[i] == '('){
                    cnt ++;
                } else if(s[i] == ')'){ 
                    cnt --;
                    if(cnt == 0){ // f 函數結束
                        cur += f(func);  // 遞迴找到這段的答案
                        func = "";
                        found = false;
                    }
                }
            } else {
                if(i == s.size() || s[i] == '*'){
                    if(!num.empty()) cur += stoi(num);
                    mul.push_back(cur); // 將數字加進去
                    num = "";
                    cur = 0;
                } else if(s[i] == '+'){
                    if(!num.empty()) cur += stoi(num); // 將數字加起來
                    num = "";
                } else if('0' <= s[i] && s[i] <= '9'){
                    num += s[i];
                }
            }
        }
        // 最後在一併把所有數字乘起來
        if(mul.empty()) return 0;
        else {
            ll ans = 1;
            for(ll x : mul) ans *= x;
            return ans;
        }   
    }
}
int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    string s;
    cin >> s;
    cout << f(s) << '\n';
}

4. 機器出租

這題算是非常經典的一道題目,在cses上有幾乎一樣的一題: Movie Festival II
部分分的解法在 CSES 上面也有: Movie Festival, 題解

這題主要是利用 greedy 的想法先將活動用結束時間排序,每次都選取結束時間最早的活動給機器。每次都選結束最早的活動才能讓機器比較快給下一個活動使用。而要分配給哪台機器則是利用 C++ 內建的 multiSet 在 O(log n)的時間來尋找可以用的機器 (結束時間比這個活動開始時間還要早)。

複雜度: O(nlogn)

主要知識: 貪心法則 (greedy method), 排序 (sorting),

#include <bits/stdc++.h>
#define ll long long
#define ft first
#define sc second
using namespace std;
bool cmp(pair <int,int> a, pair <int,int> b){
    if(a.second == b.second) return a.first > b.first;
    else return a.second < b.second;
}
int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int n, k;
    cin >> n >> k;
    multiset <int> s;
    vector <pair<int,int> > a(n);
    for(int i = 0;i < n;i++){
        cin >> a[i].ft;
    }
    for(int i = 0;i < n;i++){
        cin >> a[i].sc;
    }
    sort(a.begin(), a.end(), cmp);
    for(int i = 0;i < k;i++){
        s.insert(0);
    }
    int ans = 0;
    for(int i = 0;i < n;i++){
        auto it = s.lower_bound(a[i].first);
        if(it == s.begin()) continue;
        else {
            s.erase(--it);
            s.insert(a[i].second);
            ans++;
        }
    }
    cout << ans << '\n';
}

在〈2023 年 1 月 APCS 題解〉中有 2 則留言

  1. 「」的個人頭像
    匿名訪客

    如果測資為
    2 1
    0 2
    3 4
    結果會是1 正確答案為2

    1. 「Derick Wang」的個人頭像
      Derick Wang

      答案應該是 1 沒錯? 因為 [0, 3] 跟 [2, 4] 有重疊?

發表迴響

Blog at WordPress.com.

%d 位部落客按了讚: