Educational Codeforces Round 142 (Rated for Div. 2) A-D

空白菌發表於2023-01-25

比賽連結

A

題解

知識點:貪心。

程式碼

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

bool solve() {
    int n;
    cin >> n;
    int cnt = 0;
    for (int i = 1;i <= n;i++) {
        int x;
        cin >> x;
        if (x == 1) cnt++;
    }
    int rst = n - cnt / 2 * 2;
    cout << rst + cnt / 2 << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

B

題解

知識點:貪心。

程式碼

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

bool solve() {
    int a, b, c, d;
    cin >> a >> b >> c >> d;
    if (a == 0) {
        cout << min(b + c + d, 1) << '\n';
        return 1;
    }
    else {
        int ans = a + 2 * min(b, c) + min(a, abs(b - c) + d) + (abs(b - c) + d > a);
        cout << ans << '\n';
    }
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

C

題意

給一個長為 \(n\) 的排列,每次操作可以任選兩個數,其中小的挪到開頭,大的挪到末尾,問最少幾次操作可以使得排列有序。

題解

知識點:貪心,列舉,雙指標。

注意到,操作不影響沒有被操作過的數字的相對位置,因此考慮排列中不需要操作的數字。顯然,最終被保留的數字應該是連續上升的一個子序列,如 23456 是,而 13456 不是因為 \(1\)\(3\) 中間沒有 \(2\)

假設我們操作了某一組數 \((x,y)\) ,那麼 \((x,y),(x-1,y+1),\cdots ,(1,n)\) 一定都需要操作一遍才能保證這些數字有序。因此只有中間的數我們不需要操作,所以我們保留的數字應該從中間開始往外擴充。

\(n\) 為奇數,則從中點 \(\dfrac{1+n}{2}\) 開始往兩邊擴充套件;若 \(n\) 為偶數,先保證 \(\left\lfloor \dfrac{1+n}{2} \right\rfloor ,\left\lceil \dfrac{1+n}{2} \right\rceil\) 有序,再從這兩個數兩邊擴充套件,如果不有序直接輸出 \(\dfrac{n}{2}\)

為了方便找到某個數的位置,我們可以先處理數到位置的對映 \(pos\) ,再利用雙指標 \(l,r\) 指向擴充套件的邊界,向兩邊同時擴充套件,如果有一邊擴充套件不了那就不需要繼續了,最後結果是 \(l-1\)

程式碼

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

int pos[200007];
bool solve() {
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++) {
        int x;
        cin >> x;
        pos[x] = i;
    }
    int l = (1 + n) / 2, r = (1 + n + 1) / 2;
    if (pos[l] > pos[r]) {
        cout << n / 2 << '\n';
        return 1;
    }
    while (1 < l && r < n) {
        if (pos[l - 1] > pos[l] || pos[r] > pos[r + 1]) break;
        l--;
        r++;
    }
    cout << l - 1 << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

D

題意

給你 \(n\) 個長為 \(m\) 的排列 \(a_i\)

定義一個排列 \(p\) 的值為滿足 \(p_i = i,i \in[1,k]\)\(k\) 的最大值。

定義兩個排列 \(p,q\) 的乘法 \(p \cdot q\)\(r_i = q_{p_i}\)

對於給定的 \(n\) 個排列中,對於每個排列 \(a_i\) 找到另一個排列 \(a_j\)\(j\) 可以等於 \(i\) )使得 \(a_i \cdot a_j\) 的值最大,求出這 \(n\) 個最大值。

題解

知識點:列舉。

先考慮兩個排列 \(p,q\) 乘積的求值過程,即如何求出 \(q_{p_1} = 1,\cdots,q_{p_k} = k\)\(k\) 最大值。

顯然 \(q_{p_1} = 1\) ,即 \(q\)\(1\) 的位置是 \(p_1\) ,我們就能得到 \(k\) 至少是 \(1\) ,以此類推直到 \(q_{p_i} \neq i\) 就能得到 \(k = i-1\) ,複雜度是 \(O(n^2m^2)\) ,先考慮先最佳化列舉過程。

既然我們要知道某個數的位置,那麼我們可以先預處理出 \(q\) 所有數字出現的位置 \(pos\) 。我們發現 \(q_{p_i} = i\) 等價於 \(pos_i = p_i\) ,即 \(i\) 出現的位置等於 \(p_i\) 那麼自然可以得到 \(q_{p_i} = i\) ,由此我們從 \(pos_1 = p_1\) 開始找到最大的 \(k\) 滿足 \(pos_k = p_k\) 即可。現在複雜度是 \(O(n^2m)\) ,考慮最佳化 \(n\) 次查詢。

我們發現查詢的過程,其實就是一個 \(p\)\(n\)\(pos\) 匹配最長字首的過程,可以用字典樹 trie 解決,複雜度是 \(O(nm)\) 。但這裡 \(m\) 不大(其實是我不會字典樹),我們可以將排列用十進位制壓縮成一個整數,用 map 記錄 \(n\) 個排列的字首資訊來解決。設 \(mp_i\)\(n\) 個排列的 \(pos\)\(i\) 個數的字首資訊,例如排列 \(pos = [3,1,4,2]\) 前三個數字的資訊就是 \(314\) ,記錄在 \(mp_3\) 中。例如,我們查詢 \(p = [4,3,2,1]\)\(2\) 個數的匹配資訊時,只要判斷 \(mp_2\) 中有無 \(43\) 即可。到此為止,我們對 \(p\) 從前 \(1\) 個數依次查詢,最多查詢 \(m\) 次就可以找到最大的 \(k\) 了,複雜度是 \(O(nm\log n)\)

時間複雜度 \(O(nm \log n)\)

空間複雜度 \(O(nm)\)

程式碼

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

int a[50007][11];
int pos[11];
map<ll, int> mp[11];
bool solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1;i <= m;i++) mp[i].clear();
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j <= m;j++) {
            cin >> a[i][j];
            pos[a[i][j]] = j;
        }
        ll _t = 0;
        for (int j = 1;j <= m;j++) {
            _t = _t * 10 + pos[j] - 1;
            mp[j][_t] = 1;
        }
    }
    for (int i = 1;i <= n;i++) {
        ll _t = 0;
        int ans = m;
        for (int j = 1;j <= m;j++) {
            _t = _t * 10 + a[i][j] - 1;
            if (!mp[j][_t]) {
                ans = j - 1;
                break;
            }
        }
        cout << ans << ' ';
    }
    cout << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

相關文章