AtCoder Beginner Contest 372

~Lanly~發表於2024-09-21
省流版
  • A. 暴力即可
  • B. 轉換3進位制即可
  • C. 考慮答案的組成,僅修改發生變化的部分即可
  • D. 維護答案陣列\(ans_i\),考慮列舉 \(j\)對哪些 \(i\)有貢獻,透過單調棧找到對應的區間\(i\) ,透過差分維護區間加法即可
  • E. 並查集維護連通塊,\(set\)維護點標號大小,合併\(set\)時啟發式合併,查詢則從對應連通塊的\(set\)最大值往前找\(k\)次即可
  • F. 考慮樸素\(dp\),發現有效轉移只有 \(O(mk)\) 個,記憶化搜尋即可

A - delete . (abc372 A)

題目大意

給定一個字串,刪除其中的.

解題思路

依次判斷每個字元,如果不是.就輸出。

python可以一行,

神奇的程式碼
print(input().replace('.', ''))


B - 3^A (abc372 B)

題目大意

給定一個\(m\),將其表達成若干個 \(3\)的冪的和。

解題思路

其實就是將\(m\)轉換成三進位制,看三進位制下每一個冪的係數。

進位制轉換一下即可。

神奇的程式碼
#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 m;
    cin >> m;
    vector<int> ans;
    for (int i = 0; i <= 10; ++i) {
        int cnt = m % 3;
        while (cnt--) {
            ans.push_back(i);
        }
        m /= 3;
    }
    cout << ans.size() << '\n';
    for (auto i : ans)
        cout << i << " ";
    cout << '\n';

    return 0;
}



C - Count ABC Again (abc372 C)

題目大意

給定一個字串\(s\),進行以下\(q\)次操作。

每次操作,將\(s_x = c\)。問操作完後,字串 ABC\(s\)的出現次數。

解題思路

\(f(i) = 1/0\)表示 \(s_is_{i+1}s_{i+2}\)是/不是 ABC

答案就是\(\sum_{i = 1}^{n} f(i)\)

每次修改,其實只有 \(3\)\(f(i)\)的值可能發生變化。

因此先計算出 \(\sum_{i = 1}^{n} f(i)\),然後對於每次修改,先減去\(f(x-2)+f(x-1)+f(x)\)個 ,然後修改字元後,重新計算\(f(x-2)+f(x-1)+f(x)\)即可得到新的答案,而不需要重新計算 \(\sum_{i = 1}^{n} f(i)\)

時間複雜度就為\(O(q)\)

神奇的程式碼
#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, q;
    string s;
    cin >> n >> q >> s;
    int ans = 0;
    auto in_range = [&](int i) { return i >= 0 && i < n; };
    auto check = [&](int x) {
        for (int i = 0; i < 3; ++i)
            if (!in_range(x + i) || s[x + i] != 'A' + i)
                return false;
        return true;
    };
    for (int i = 0; i < n; ++i) {
        ans += check(i);
    }
    while (q--) {
        int x;
        string c;
        cin >> x >> c;
        --x;
        for (int i = x - 2; i <= x; ++i) {
            ans -= check(i);
        }
        s[x] = c[0];
        for (int i = x - 2; i <= x; ++i) {
            ans += check(i);
        }
        cout << ans << '\n';
    }

    return 0;
}



D - Buildings (abc372 D)

題目大意

給定\(n\)個數的陣列 \(a\),對於每個 \(i \in [1,n]\) ,問\(j\)的數量滿足 \((i,j]\)\(a_j\)是最大值。

解題思路

如果列舉\(i\),求 \(j\),複雜度是 \(O(n^2)\)

反過來,列舉 \(j\),考慮它會對哪些 \(i\)\(1\)的貢獻。 會發現只要滿足\(\max(a[i+1..j]) \leq j\),那麼這個 \(j\)就會對 \(i\)\(1\)的貢獻。

即對於每個數\(a_j\),我們找其左邊第一個\(a_l > a_j\),那麼\(ans[l..j-1] += 1\)

對於第一個,如何找到其左邊第一個\(a_l > a_j\)呢?這是一個經典問題,從右往左,用單調棧維護一個遞減的數列即可(棧頂到棧底是遞減的)。

對於第二個,有若干次區間加法,但最後只有一次查詢,可以用差分陣列將區間加變成單點修改,最後還原出答案陣列即可。

神奇的程式碼
#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;
    cin >> n;
    vector<int> h(n);
    for (auto& x : h)
        cin >> x;
    stack<int> s;
    vector<int> diff(n + 1);
    for (int i = n - 1; i >= 0; i--) {
        while (!s.empty() && h[s.top()] < h[i]) {
            diff[i]++;
            diff[s.top()]--;
            s.pop();
        }
        s.push(i);
    }
    while (!s.empty()) {
        diff[0]++;
        diff[s.top()]--;
        s.pop();
    }
    int ans = 0;
    for (int i = 0; i < n; ++i) {
        ans += diff[i];
        cout << ans << " \n"[i == n - 1];
    }

    return 0;
}



E - K-th Largest Connected Components (abc372 E)

題目大意

給定\(n\)個點,初始無邊。維護兩類操作。

  • 1 u v,連一條無向邊\(u \to v\)
  • 2 v k,找\(v\)所在連通塊的第 \(k\)大的點的標號。

\(k \leq 10\)

解題思路

注意到\(k \leq 10\),如果能用 \(set\)維護一個連通塊的所有點的標號,那麼直接從 rbegin()暴力數\(k\)個就是答案,

如果維護這個 \(set\)呢?連通塊的資訊自然用並查集維護,當合並兩個連通塊時,其 \(set\)也要合併,我們可以將小的 \(set\)合併到大的 \(set\)中,即啟發式合併,每次合併 \(set\)最壞的複雜度是 \(O(n)\),但最壞情況下,合併後的 \(set\)大小會翻倍,最多翻倍 \(log\)次就達到 \(n\)個點,即最壞情況的出現次數只有 \(O(\log n)\)次,因此啟發式合併的時間複雜度是 \(O(n \log n)\)

維護好 \(set\)後,直接從 \(rbegin()\)\(k\)個點即為答案。特判少於\(k\)個點的情況。

時間複雜度是 \(O((n + qk) \log n)\)

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

class dsu {
  public:
    vector<int> p;
    vector<int> sz;
    vector<set<int>> node;
    int n;

    dsu(int _n) : n(_n) {
        p.resize(n);
        sz.resize(n);
        node.resize(n);
        iota(p.begin(), p.end(), 0);
        fill(sz.begin(), sz.end(), 1);
        for (int i = 0; i < n; i++) {
            node[i].insert(i);
        }
    }

    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) {
            if (sz[x] < sz[y])
                swap(x, y);
            node[x].insert(node[y].begin(), node[y].end());
            p[y] = x;
            sz[x] += sz[y];
            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);
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            int u, v;
            cin >> u >> v;
            --u, --v;
            d.unite(u, v);
        } else {
            int v, k;
            cin >> v >> k;
            --v;
            int fa = d.get(v);
            if (d.node[fa].size() < k) {
                cout << -1 << '\n';
            } else {
                auto it = d.node[fa].rbegin();
                advance(it, k - 1);
                cout << *it + 1 << '\n';
            }
        }
    }

    return 0;
}



F - Teleporting Takahashi 2 (abc372 F)

題目大意

給定一張有向圖,它首先是個環,然後在此基礎上加了\(m \leq 50\)條有向邊。

問從 \(1\)號點出發,走 \(k\)個點,所形成的點的路徑 \((1,v_1,v_2,...,v_k)\)的情況數。

解題思路

考慮樸素搜尋,設\(dp[u][k]\)表示從 \(u\)號點走 \(k\)步的方案數,顯然其答案就是 \(\sum_{u \to v}dp[v][k-1]\)

但這複雜度是 \(O(nk)\),顯然過不了。

但注意到有很多點 \(u \to v\)\(v\)其實只有一個即 \(u+1\),即只有一種走法,它們的轉移顯然是沒必要的。我們可以只考慮有分叉點的 \(u\)。而這有分叉點的 \(u\)最多隻有 \(m\)個。因此如果我們的轉移是從 $u \to $分叉點,這樣的時間複雜度就是 \(O(mk)\), 是可以透過的。

記當前點\(u\),然後找到 \(u\)後第一個分叉點 \(x\), 其出度\(> 1\),那麼 \(dp[u][k] = \sum_{x \to y}dp[y][k^\prime]\),轉移後\(k^\prime\)可以透過簡單的運算得到,即\(k - dis(u \to x) - 1\)

有效的\(u\)就只有 \(2m\)個,對 \(dp\)進行記憶化搜尋,複雜度即為 \(O(mk)\)。因為用\(map\)來記憶化會超時,所以只好離散化有效的 \(u\),轉成陣列來記憶化了。

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

const int mo = 998244353;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m, k;
    cin >> n >> m >> k;
    vector<vector<int>> edge(n);
    for (int i = 0; i < n; i++) {
        edge[i].push_back((i + 1) % n);
    }
    vector<int> tr;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        u--;
        v--;
        edge[u].push_back(v);
        tr.push_back(u);
    }
    for (int i = 0; i < n; i++) {
        sort(edge[i].begin(), edge[i].end());
        edge[i].erase(unique(edge[i].begin(), edge[i].end()), edge[i].end());
    }
    sort(tr.begin(), tr.end());
    tr.erase(unique(tr.begin(), tr.end()), tr.end());
    auto calc = [&](int u, int v) -> int {
        if (u > v)
            v += n;
        return v - u;
    };
    int ans = 0;
    if (m == 0) {
        ans = 1;
    } else {
        vector<int> nxt(n);
        for (int i = 0; i < n; i++) {
            auto it = lower_bound(tr.begin(), tr.end(), i);
            if (it == tr.end())
                it = tr.begin();
            nxt[i] = *it;
        }
        vector<int> id(n, -1);
        int cnt = 0;
        auto dfs_pre = [&](auto& dfs, int u) -> void {
            if (id[u] != -1)
                return;
            id[u] = cnt++;
            int it = nxt[u];
            for (int v : edge[it]) {
                dfs(dfs, v);
            }
        };
        dfs_pre(dfs_pre, 0);
        vector<vector<int>> dp(k + 1, vector<int>(cnt, -1));

        auto dfs = [&](auto& dfs, int u, int k) -> int {
            if (k == 0) {
                return 1;
            }
            if (dp[k][id[u]] != -1)
                return dp[k][id[u]];
            int it = nxt[u];
            int sum = 0;
            int nxt = (u + 1) % n;
            int cost = calc(u, it);
            if (cost >= k)
                return 1;
            for (int v : edge[it]) {
                sum += dfs(dfs, v, k - cost - 1);
                if (sum >= mo)
                    sum -= mo;
            }
            return dp[k][id[u]] = sum;
        };
        ans = dfs(dfs, 0, k);
    }
    cout << ans << '\n';

    return 0;
}



G - Ax + By < C (abc372 G)

題目大意

給定三個\(n\)個數的陣列\(a,b,c\),問 \((x,y)\)的數量,滿足

  • \(a_i x + b_i < c\),對任意 \(i \in [1,n]\)

多組資料。

解題思路

一眼只會\(O(na_i)\)

神奇的程式碼



相關文章