AtCoder Beginner Contest 380

~Lanly~發表於2024-11-17
省流版
  • A. 計數判斷即可
  • B. 計數統計即可
  • C. 模擬即可
  • D. 注意到字串是左右大小寫映象,從長度大往小依次考慮實際所處的位置,維護映象次數集合
  • E. 並查集維護連通性,並嘗試與左右倆格子合併即可
  • F. 博弈\(dp\),狀態數只有 \(5e5\),直接記憶化搜尋即可
  • G. 列舉打亂起始位置,全排列分成三部分,考慮逆序對來源的\(6\)個部分,注意到打亂部分的逆序對期望數量為定值,其餘 \(5\)部分用權值樹狀陣列或權值線段樹維護即可

A - 123233 (abc380 A)

題目大意

給了\(6\)個數字,問是否

  • \(1\)的數字出現 \(1\)
  • \(2\)的數字出現 \(2\)
  • \(3\)的數字出現 \(3\)

解題思路

統計每個數字出現的次數即可。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    string s;
    cin >> s;
    bool ok = true;
    for (int i = 1; i <= 3; ++i)
        ok &= count(s.begin(), s.end(), i + '0') == i;
    if (ok)
        cout << "Yes" << '\n';
    else
        cout << "No" << '\n';

    return 0;
}



B - Hurdle Parsing (abc380 B)

題目大意

給定一個包含-|的字串。統計每兩個|之間有多少個-

解題思路

找到連續的兩個|的下標,然後其差即為答案。Python可以一行。

神奇的程式碼
print(' '.join([str(len(s)) for s in input()[1:-1].split('|')]))


C - Move Segment (abc380 C)

題目大意

給定一個01子串。

連續的1視為一個塊。

現將第\(k\)個塊移動到第 \(k-1\)個塊前面。

解題思路

按照題意,找到第\(k\)個塊的下標,然後複製即可。下述是Python程式碼,split('0')可以輕易找到第\(k\)個塊,然後移動即可。

神奇的程式碼
n, k = map(int, input().split())
k -= 1
s = input().split('0')
one = [pos for pos, i in enumerate(s) if i]
kk = s[one[k]]
s.pop(one[k])
s[one[k - 1]] += kk + '0'
print('0'.join(s))


D - Strange Mirroring (abc380 D)

題目大意

給定一個字串\(s\),重複下述操作 無數次:

  • \(s\)的字母大小寫反轉成 \(t\),加到 \(s\)後面

給定 \(q\)個詢問,每個詢問問第 \(k\)個字元是什麼。

解題思路

每進行一次操作,字串\(s\)的長度會翻倍。

容易發現其字串形如\(s_0\overline{s_0}\overline{s_0\overline{s_0}}\overline{s_0\overline{s_0}\overline{s_0\overline{s_0}}}\overline{s_0\overline{s_0}\overline{s_0\overline{s_0}}\overline{s_0\overline{s_0}\overline{s_0\overline{s_0}}}}\)

我們從大往小的看,左右兩邊長度一樣,區別就是反轉。我們找到第一個\(s_i\),使得\(k\)在右半邊,那我們就記錄一次反轉操作,然後遞迴的考慮右邊橫線下方的字串,直到 \(k < |s|\),我們就得到對應的字元和反轉的次數,就得知最後的字元是多少了。

即先找到第一個 \(s_i = s_{i-1}t_{i-1}\),其中 \(k\)位於 \(t_{i-1}\)裡,這裡\(s_{i-1}\)\(t_{i-1}\)只是大小寫反了。此時就需要反轉一次了,然後令\(k = k - |s_{i-1}|\),遞迴的考慮重複考慮相同的情況即可。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    string s;
    int q;
    cin >> s >> q;
    vector<LL> round{int(s.size())};
    auto dfs = [&](auto dfs, LL k, int sign) -> char {
        if (k < s.size()) {
            if (sign) {
                if (isupper(s[k])) {
                    return tolower(s[k]);
                } else {
                    return toupper(s[k]);
                }
            } else {
                return s[k];
            }
        }
        auto pos = upper_bound(round.begin(), round.end(), k);
        LL len = *pos / 2;
        return dfs(dfs, k - len, sign ^ 1);
    };
    while (q--) {
        LL k;
        cin >> k;
        --k;
        while (round.back() <= k) {
            round.push_back(round.back() * 2);
        }
        cout << dfs(dfs, k, 0) << ' ';
    }
    cout << '\n';

    return 0;
}



E - 1D Bucket Tool (abc380 E)

題目大意

從左到右\(n\) 個格子,第 \(1\) 個格子顏色為\(i\)

維護 \(q\) 個查詢,分兩種:

  • 1 x c:將第\(x\)個格子連同周圍與其同色的格子塗成顏色\(c\)
  • 2 c:問顏色\(c\)的格子數

解題思路

如果一個格子顏色與周圍相同,則它們就會連通,成為一個整體,我們可以用並查集維護這個連通性。

對於操作\(1\),從並查集就很方便的修改一個整體的顏色,進而維護每種顏色的格子數。但修改顏色後,需要看看這個整體與左右兩個格子的顏色是否相同,能否合併成新的整體。

為了能找到這個整體的左右相鄰格子,並查集需要維護該整體的最左和最右的格子,然後根據顏色決定是否合併。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

class dsu {
  public:
    vector<int> p;
    vector<int> l;
    vector<int> r;
    vector<int> sz;
    vector<int> col;
    int n;

    dsu(int _n) : n(_n) {
        p.resize(n);
        col.resize(n);
        sz.resize(n);
        l.resize(n);
        r.resize(n);
        iota(l.begin(), l.end(), 0);
        iota(r.begin(), r.end(), 0);
        iota(p.begin(), p.end(), 0);
        iota(col.begin(), col.end(), 0);
        fill(sz.begin(), sz.end(), 1);
    }

    inline int get(int x) { return (x == p[x] ? x : (p[x] = get(p[x]))); }

    inline bool unite(int x, int y) {
        x = get(x);
        y = get(y);
        if (x != y) {
            p[x] = y;
            sz[y] += sz[x];
            l[y] = min(l[y], l[x]);
            r[y] = max(r[y], r[x]);
            return true;
        }
        return false;
    }
};

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, q;
    cin >> n >> q;
    dsu d(n);
    vector<int> cnt(n, 1);
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            int x, c;
            cin >> x >> c;
            x--;
            --c;
            int fx = d.get(x);
            cnt[d.col[fx]] -= d.sz[fx];
            d.col[fx] = c;
            cnt[d.col[fx]] += d.sz[fx];
            for (auto& nei : {d.l[fx] - 1, d.r[fx] + 1}) {
                if (nei >= 0 && nei < n) {
                    int fnei = d.get(nei);
                    if (fnei != fx && d.col[fnei] == c) {
                        d.unite(fx, fnei);
                    }
                }
            }
        } else {
            int c;
            cin >> c;
            --c;
            cout << cnt[c] << '\n';
        }
    }

    return 0;
}



F - Exchange Game (abc380 F)

題目大意

高橋和青木手裡各有\(n,m\)張牌,桌子上有 \(l\)張牌。牌上寫了數字。

高橋和青木輪流操作,直到無法操作的人輸。

操作為,將手裡的一張牌 \(a\)放到桌子上,如果桌上有數字小於 \(a\)的牌,他可以拿一張到自己手上,當然也可以選擇不拿。

高橋先手,問最優策略下誰贏。

解題思路

博弈\(dp\),從卡牌在誰手中的角度思考狀態數,只有 \(O(3^{n+m+l}) = O(3^12) = 5e5\),因此直接搜尋即可。

即設 \(dp[i][j]=1/0\)表示當前 \(i\)先手,局面是 \(j\)(即一個描述\(n+m+l\)張牌在誰手中的狀態,可以是一個陣列,\(0/1/2\)分別表示在高橋、青木或者桌子上,也可以是一個三進位制的壓縮狀態),此時先手必贏/必輸。

轉移則列舉當前手的策略,即先花\(O(n+m+l)\)列舉將手裡的哪張牌放到桌子上,再花 \(O(n+m+l)\)列舉 拿桌子上的哪張牌,然後更新局面狀態,轉移到下一個局面即可。

而當前局是先手必贏/必輸,取決於下一個局面(注意下一個局面的先手是當前局面的後手)。因為是最優策略,因此下一個局面如果有(當前局面的後手)必輸局面,那先手必贏,否則如果下一個局面全是(當前局面的後手)必贏局面,則先手必輸。

因此求解是一個遞迴的過程。總的時間複雜度是\(O((n+m+l)^2 3^{n+m+l})\)

vector狀態1700ms
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m, l;
    cin >> n >> m >> l;
    vector<int> c(n + m + l);
    for (auto& x : c)
        cin >> x;
    array<map<vector<int>, int>, 2> dp;
    vector<int> st(n + m + l);
    fill(st.begin(), st.begin() + n, 0);
    fill(st.begin() + n, st.begin() + n + m, 1);
    fill(st.begin() + n + m, st.end(), 2);
    auto dfs = [&](auto dfs, vector<int>& st, int p) -> bool {
        if (count(st.begin(), st.end(), p) == 0) {
            return dp[p][st] = 0;
        }
        if (dp[p].count(st))
            return dp[p][st];
        bool ok = true;
        for (int i = 0; i < n + m + l; i++) {
            if (st[i] == p) {
                st[i] = 2;
                for (int j = 0; j < n + m + l; j++) {
                    if (st[j] == 2 && c[j] < c[i]) {
                        st[j] = p;
                        ok &= dfs(dfs, st, p ^ 1);
                        st[j] = 2;
                    }
                }
                ok &= dfs(dfs, st, p ^ 1);
                st[i] = p;
            }
        }
        return dp[p][st] = (ok ^ 1);
    };

    if (dfs(dfs, st, 0))
        cout << "Takahashi" << '\n';
    else
        cout << "Aoki" << '\n';

    return 0;
}



三進位制壓縮狀態200ms
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m, l;
    cin >> n >> m >> l;
    vector<int> c(n + m + l);
    for (auto& x : c)
        cin >> x;
    vector<int> base(n + m + l, 1);
    for (int i = 1; i < n + m + l; ++i) {
        base[i] = base[i - 1] * 3;
    }
    array<vector<int>, 2> dp{vector<int>(base.back() * 3, -1),
                             vector<int>(base.back() * 3, -1)};
    auto get_bit = [&](int x, int y) { return x / base[y] % 3; };
    auto tr = [&](int& cur, int pos, int v) {
        cur -= get_bit(cur, pos) * base[pos];
        cur += v * base[pos];
    };
    auto final = [&](int st, int p) {
        for (int i = 0; i < n + m + l; i++) {
            if (get_bit(st, i) == p)
                return false;
        }
        return true;
    };

    auto dfs = [&](auto dfs, int st, int p) -> bool {
        if (final(st, p)) {
            return dp[p][st] = 0;
        }
        if (dp[p][st] != -1)
            return dp[p][st];
        bool ok = true;
        for (int i = 0; i < n + m + l; i++) {
            if (get_bit(st, i) == p) {
                tr(st, i, 2);
                for (int j = 0; j < n + m + l; j++) {
                    if (get_bit(st, j) == 2 && c[j] < c[i]) {
                        tr(st, j, p);
                        ok &= dfs(dfs, st, p ^ 1);
                        tr(st, j, 2);
                    }
                }
                ok &= dfs(dfs, st, p ^ 1);
                tr(st, i, p);
            }
        }
        return dp[p][st] = (ok ^ 1);
    };

    int st = 0;
    for (int i = 0; i < n + m + l; i++) {
        tr(st, i, (i >= n) + (i >= n + m));
    }

    if (dfs(dfs, st, 0))
        cout << "Takahashi" << '\n';
    else
        cout << "Aoki" << '\n';

    return 0;
}


G - Another Shuffle Window (abc380 G)

題目大意

給定一個關於\(n\)的全排列\(p\)和一個數字 \(k\)。進行如下操作一次。

  • 第一步,從\([1, n - k + 1]\)隨機選一個數\(i\)
  • 第二部,將 \(p_{i, i + k - 1}\)隨機打亂

問進行操作後的逆序對的期望值。

解題思路

期望的求法就是該情況的逆序對數$\times $發生該情況的機率,對所有情況求和。因此我們先考慮所有情況怎麼來的。

首先第一步,列舉\(i\),它數量只有 \(O(n)\),可以列舉。

列舉了 \(i\)後,排列 \(p\)就分成了三部分: \(l,m,r\),其中 \(m\)就是將會隨機打亂的\(k\)個數,然後\(l,r\)就是左右兩部分的數。

再然後就是第二部,將中間部分\(m\)打亂,但這情況數有\(O(k!)\),顯然不能列舉,我們考慮能否綜合第二步的所有情況來求。

考慮逆序對\((i,j)\)的來源,根據\(i,j\)的值,有這幾部分:

  • \(ll\)部分
  • \(lr\)部分
  • \(lm\)部分
  • \(rr\)部分
  • \(mr\)部分
  • \(mm\)部分

除了最後一個,前\(5\)個的逆序對的數量不會隨著第二步的操作而改變。當第一個的\(i\)變化時,我們可以實時維護前 \(5\)部分的逆序對數量的變化。

而第六部分的 \(mm\),考慮裡面的任意兩個數 \((i,j)\),綜合所有的隨機打亂情況 ,誰前誰後其實各佔一半,也就是說,任意一對數只有一半的機率會產生逆序對,而\(k\)個數一共有 \(\frac{k(k-1)}{2}\)對數,每對形成逆序對的機率都是 \(\frac{1}{2}\),因此第六部分的期望逆序對數量始終是 \(\frac{k(k-1}}{2} \frac{1}{2} = \frac{k(k-1)}{4}\)

而前 \(5\)部分的維護,即 \(m\)最左邊少一個數, \(l\)最右邊多一個數, \(m\) 最右邊多一個數,\(r\)最左邊少一個數,依次計算這些數所產生的逆序對數量,然後更新即可。

而產生逆序對的數量,即對於數\(i\) ,我們想知道大於\(i\)或小於 \(i\)的數量,可以透過維護 \(l,m,r\)部分的桶(即\(cnt_{i}\)表示數字\(i\)出現的次數 ),加以樹狀陣列或線段樹的單點修改和區間求和,從而在 \(O(\log n)\)的時間得到。即權值線段樹或權值樹狀陣列。

總的時間複雜度為\(O(n \log n)\)

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

// starting from 1
template <typename T> class fenwick {
  public:
    vector<T> fenw;
    int n;
    int tot;

    fenwick(int _n) : n(_n) {
        fenw.resize(n);
        tot = 0;
    }

    inline int lowbit(int x) { return x & -x; }

    void modify(int x, T v) {
        tot += v;
        for (int i = x; i < n; i += lowbit(i)) {
            fenw[i] += v;
        }
    }

    T get(int x) {
        T v{};
        for (int i = x; i > 0; i -= lowbit(i)) {
            v += fenw[i];
        }
        return v;
    }
};

const LL mo = 998244353;

long long qpower(long long a, long long b) {
    long long qwq = 1;
    while (b) {
        if (b & 1)
            qwq = qwq * a % mo;
        a = a * a % mo;
        b >>= 1;
    }
    return qwq;
}

long long inv(long long x) { return qpower(x, mo - 2); }

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, k;
    cin >> n >> k;
    vector<int> p(n);
    fenwick<int> l(n + 1), mid(n + 1), r(n + 1);
    for (auto& x : p) {
        cin >> x;
    }
    LL llc = 0, lmc = 0, mrc = 0, rrc = 0, lrc = 0;
    LL mmc = 1ll * k * (k - 1) % mo * inv(4) % mo;

    const int large = 1;
    const int small = 0;
    auto get_cnt = [&](fenwick<int>& f, int p, int large) {
        if (!large)
            return f.get(p - 1);
        else
            return f.tot - f.get(p);
    };
    auto update_l = [&](int num, int v) { // the right of l
        l.modify(num, v);
        llc += get_cnt(l, num, large) * v;
        lmc += get_cnt(mid, num, small) * v;
        lrc += get_cnt(r, num, small) * v;
    };
    auto update_m = [&](int num, int v) {
        mid.modify(num, v);
        lmc += get_cnt(l, num, large) * v;
        mrc += get_cnt(r, num, small) * v;
    };
    auto update_r = [&](int num, int v) { // the left of r
        r.modify(num, v);
        lrc += get_cnt(l, num, large) * v;
        mrc += get_cnt(mid, num, large) * v;
        rrc += get_cnt(r, num, small) * v;
    };

    for (int i = 0; i < k; ++i)
        update_m(p[i], 1);
    for (int i = n - 1; i >= k; --i)
        update_r(p[i], 1);
    LL ans = (llc + lmc + mrc + rrc + lrc + mmc) % mo;
    for (int i = 0; i < n - k; ++i) {
        update_m(p[i], -1);
        update_l(p[i], 1);
        update_r(p[i + k], -1);
        update_m(p[i + k], 1);
        ans = (ans + llc + lmc + mrc + rrc + lrc + mmc) % mo;
    }
    ans = ans * inv(n - k + 1) % mo;
    cout << ans << '\n';

    return 0;
}