【2024暑#108】ACM暑期第三次測驗(個人賽)

Martian148發表於2024-08-03

A - 貓抓老鼠

經典的逆序對問題,這裡就不過多闡述了

有遞迴和樹狀陣列兩種寫法,自行百度即可

B - 字元變換

檢視 \((S[i]-T[i])\%26\) 是否相同即可

#include <bits/stdc++.h>
using namespace std;
int main() {
    string S, T; cin >> S >> T;
    set<int> st;
    for (int i = 0; i < S.size(); i++) {
        int cz = (S[i] - T[i] + 26) % 26;
        st.insert(cz);
    }
    cout << (st.size() == 1 ? "Yes" : "No") << endl;
    return 0;
}

C - 7777777

首先,如果我們刪除了一些 7,如果仍然存在從原點看不到的 7,則我們也可以刪除這些 7,而不會改變從原點看到的 7 的數量。

因此,問題可以重新表述如下:

給定 \(N\) 個 7,你可以刪除其中一些,以便所有剩餘的 7 都可以從原點看到。找到剩餘 7 的最大數量。

此外,我們可以把每個 7 視為一個開區間,區間的兩端對應於其兩個端點的極角,這樣問題就可以重新表述如下:

給定 \(N\) 個開放區間。第 \(i\) 個區間的左端點為 \(f(x_i,y_i-1)\),右端點為 \(f(x_i-1,y_i)\),其中 \(f(i,j)\) 是一個由兩個實數 \((i,j)\) 表示座標 \((i,j)\) 極角的函式。

最多可以選擇多少個區間,使得任意兩個區間不重疊?

這就是著名的區間排程問題。可以透過以下貪心演算法解決,同樣可以應用於這個問題。

重複以下操作。

  1. \(R_{\max}\) 成為已選擇區間中(右端點的)最大座標。如果存在尚未選擇的區間,其左端點座標大於或等於 \(R_{\max}\),則選擇其中右端點最小的區間。
  2. 否則,如果不存在左端點座標大於等於 \(R_{\max}\) 的區間,則終止過程。

在實現時,將 \(N\) 個區間按其右端點座標的遞增順序排序。然後維護 \(R_{\max}\),如果某個區間的左端點大於等於 \(R_{max}\),那麼直接把 \(R_{\max}\) 更新成當前區間的右端點

時間複雜度為 \(O(N \log N)\)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct Frac {
    ll p, q; // p / q
    Frac(ll p = 0, ll q = 1) : p(p), q(q) {}
    bool operator < (const Frac &f) const { return p * f.q < q * f.p; }
    bool operator <= (const Frac &f) const { return p * f.q <= q * f.p; }
};

int main() {
    int N; cin >> N;
    vector<ll> x(N), y(N);
    for (int i = 0; i < N; i++) cin >> x[i] >> y[i];
    vector<pair<Frac, Frac>> que(N);
    for (int i = 0; i < N; i++) que[i] = make_pair(Frac(y[i], x[i] - 1), Frac(y[i] - 1, x[i]));
    sort(que.begin(), que.end());
    int cnt = 0;
    Frac R_max = Frac(0, 1);
    for (auto cur : que) {
        if (R_max <= cur.second) { // cur.second 是左端點
            cnt += 1;
            R_max = cur.first; // 更新右端點
        }
    }
    cout << cnt << endl;
    return 0;
}

D - 武術大師

可以使用記憶化 DFS,當然,由於滿足 \(A_{i,j} < i\) 說明需要在 \(i\) 之前學的招式都是在 \(i\) 之前出現過的,所以我們從後往前,記錄一個陣列 \(used[i]\) 表示 \(i\) 是否需要學習,如果 \(used[i]=1\) 表示需要學習,那麼把 \(i\) 之前需要學的招式 \(j\) 都標記為 \(1\)

注意,這裡的 \(A_{i,j}\) 最好使用 \(vector\) 來儲存

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    int n; cin >> n;
    vector<vector<int>> p(n, vector<int>());
    vector<int> T(n), K(n);
    for (int i = 0; i < n; i++) {
        cin >> T[i] >> K[i];
        for (int j = 0; j < K[i]; j++) {
            int x; cin >> x;
            p[i].push_back(x);
        }
    }
    vector<int> used(n, 0); used[n - 1] = 1;
    for (int i = n - 1; i >= 0; i--) {
        if (used[i]) {
            for (int j = 0; j < K[i]; j++) {
                used[p[i][j] - 1] = 1;
            }
        }
    }
    long long ans = 0;
    for (int i = 0; i < n; i++) 
        ans += T[i] * used[i];
    cout << ans << endl;
    return 0;
}

E - 掃雷

因為 \(N\) 很小,直接暴力 dfs 就可以了

但是正解是使用 DP 來做的

定義 \(F[i]\) 表示以第 \(i\) 節點結束的最大值,則

\[f[i]=\max{f[j]}+a[i](g[i][j]=1) \]

對於輸出,使用 \(pre[i]\) 記錄 \(i\) 的前驅節點,然後遞迴輸出即可

#include <bits/stdc++.h>
using namespace std;

int main() {
    freopen ("E.in", "r", stdin);
    int n; cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    vector<vector<int>> g(n + 1, vector<int>(n + 1, 0));
    vector<int> f(n + 1, 0), pre(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            int x; cin >> x;
            g[i][j] = x;
        }
    }
    int ans = -1, ans_i;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < i; j++) {
            if (g[j][i] == 1 && f[j] > f[i]) {
                f[i] = f[j];
                pre[i] = j;
            }
        }
        f[i] += a[i];
        if (f[i] > ans) {
            ans = f[i];
            ans_i = i;
        }
    }

    auto print = [&](auto && print, int x) -> void {
        if (pre[x] == 0) {
            cout << x << " ";
            return;
        }
        print(print, pre[x]);
        cout << x << " ";
    };

    print(print, ans_i);
    cout << endl;
    cout << ans << endl;
    return 0;
}

F - 整除(middle)

還是容斥原理

定義 \(A=\{\text{能被2整除}\},B=\{\text{能被3整除}\},C=\{能被7整除\},U=\text{全集}\)

那麼所求的就是 \((U-A)(U-B)C=U^2C-UAC-UBC+UABC=C-AC-BC+ABC\)

寫成式子的形式就是:

\[\frac{n}{7}-\frac{n}{2\times 7}-\frac{n}{3\times 7} +\frac{n}{2\times 3\times 7} \]

#include<bits/stdc++.h>
using namespace std;
int main() {  
    long long n;  
    cin>>n;
    long long sum= (n/7)-(n/14)-(n/21)+(n/42);  
    cout<<sum<<endl;  
    return 0;  
}

相關文章