Sciseed Programming Contest 2021(AtCoder Beginner Contest 219)【A - G】


比賽鏈接:https://atcoder.jp/contests/abc219/tasks

A - AtCoder Quiz 2

題意

給出一個分數 \(x\) ,共分為四級:

  • \(0 \le x \lt 40\)
  • \(40 \le x \lt 70\)
  • \(70 \le x \lt 90\)
  • \(90 \le x\)

輸出 \(x\) 到下一級所需的分數,如已是最高級輸出 expert

題解

模擬。

代碼

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int x;
    cin >> x;
    if (x >= 90) {
        cout << "expert" << "\n";
    } else {
        cout << (x < 40 ? 40 - x : (x < 70 ? 70 - x : 90 - x)) << "\n";
    }
    return 0;
}

B - Maritozzo

題意

給出 \(3\) 個字符串 \(s_i\) ,按字符串 \(t\) 中的順序依次輸出。

題解

模擬。

代碼

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    vector<string> s(3);
    for (int i = 0; i < 3; i++) {
        cin >> s[i];
    }
    string t;
    cin >> t;
    for (auto ch : t) {
        cout << s[ch - '1'];
    }
    return 0;
}

C - Neo-lexicographic Ordering

題意

\(n\) 個字符串按照給定的某種字典序排序。

題解

模擬。

代碼

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    string mp;
    cin >> mp;
    int n;
    cin >> n;
    vector<pair<string, string>> s(n);
    for (auto& [x, y] : s) {
        cin >> y;
        x = y;
        for (auto& ch : x) {
            ch = mp.find(ch) + 'a';
        }
    }
    sort(s.begin(), s.end());
    for (auto [x, y] : s) {
        cout << y << "\n";
    }
    return 0;
}

D - Strange Lunchbox

題意

\(n\) 個物品,每個物品的價值為 \((a_i, b_i)\) ,計算最少要選取多少個物品使得 \(\sum a \ge x\) \(\sum b \ge y\)

  • \(1 \le n \le 300\)
  • \(1 \le a_i, b_i \le 300\)
  • \(1 \le x, y \le 300\)

題解

\(01dp\) ,設 \(dp[i][j]\)\(\sum a = i\) \(\sum b = j\) 的最小花費。

注意對於 \(\sum a \ge x\) \(\sum b \ge y\) 的狀態的壓縮。

代碼

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    int x, y;
    cin >> x >> y;
    vector<int> a(n), b(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i] >> b[i];
    }
    constexpr int N = 305;
    vector<vector<int>> dp(N, vector<int> (N, 1e9));
    dp[0][0] = 0;
    for (int i = 0; i < n; i++) {
        auto next_dp(dp);
        for (int j = 0; j < N; j++) {
            for (int k = 0; k < N; k++) {
                next_dp[min(N - 1, j + a[i])][min(N - 1, k + b[i])] = min(next_dp[min(N - 1, j + a[i])][min(N - 1, k + b[i])], dp[j][k] + 1);
            }
        }
        dp = next_dp;
    }
    int ans = 1e9;
    for (int i = x; i < N; i++) {
        for (int j = y; j < N; j++) {
            ans = min(ans, dp[i][j]);
        }
    }
    cout << (ans == 1e9 ? -1 : ans) << "\n";
    return 0;
}

E - Moat

題意

給出一個 \(4 \times 4\)\(01\) 矩陣,計算有多少矩陣多邊形可以包含所有 \(1\)

題解

矩陣多邊形可以視為無內環的連通塊,枚舉每個塊的選取狀態即可,共 \(2^{16}\) 種情況。

關於連通塊合法性的判斷:

  • 初始時有 \(16\) 個連通塊,之后根據選取狀態合並為全 \(0/1\) 連通塊,為了判斷是否有內環,可以將外圍的全 \(0\) 連通塊合並到第 \(17\) 個連通塊中,這樣如果最后只剩兩個連通塊即說明當前選取狀態合法。

代碼

#include <bits/stdc++.h>
using namespace std;
const int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

struct dsu {
    vector<int> fa, sz;
 
    dsu(int n) : fa(n), sz(n) {
        iota(fa.begin(), fa.end(), 0);
        fill(sz.begin(), sz.end(), 1);
    }
 
    int Find(int x) {
        return fa[x] == x ? x : fa[x] = Find(fa[x]);
    }
 
    void Union(int x, int y) {
        x = Find(x), y = Find(y);
        if (x == y) {
            return;
        }
        if (sz[x] < sz[y]) {
            swap(x, y);
        }
        sz[x] += sz[y];
        fa[y] = x;
    }
 
    int Size(int x) {
        return fa[x] == x ? sz[x] : sz[x] = sz[Find(x)];
    }
 
    bool diff(int x, int y) {
        return Find(x) != Find(y);
    }

    int Groups() {
        int res = 0;
        for (int i = 0; i < (int)fa.size(); i++) {
            if (fa[i] == i) {
                ++res;
            }
        }
        return res;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    vector<vector<int>> a(4, vector<int> (4));
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            cin >> a[i][j];
        }
    }
    int ans = 0;
    for (int mask = 0; mask < (1 << 16); mask++) {
        vector<vector<int>> b(4, vector<int> (4));
        for (int i = 0; i < 16; i++) {
            if (mask & (1 << i)) {
                b[i / 4][i % 4] = 1;
            }
        }
        bool ok = true;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (a[i][j] == 1 and b[i][j] == 0) {
                    ok = false;
                }
            }
        }
        if (not ok) {
            continue;
        }
        dsu dsu(17);
        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                for (int i = 0; i < 4; i++) {
                    int nx = x + dir[i][0], ny = y + dir[i][1];
                    if (not (0 <= nx and nx < 4 and 0 <= ny and ny < 4)) {
                        if (b[x][y] == 0) {
                            dsu.Union(x * 4 + y, 16);
                        }
                    } else {
                        if (b[nx][ny] == b[x][y]) {
                            dsu.Union(x * 4 + y, nx * 4 + ny);
                        }
                    }
                }
            }
        }
        if (dsu.Groups() == 2) {
            ++ans;
        }
    }
    cout << ans << "\n";
    return 0;
}

F - Cleaning Robot

題意

給出一個點在二維平面的運動軌跡 \(s\) ,初始時點在 \((0, 0)\) 處,問將 \(s\) 連續執行 \(k\) 次后共經過了多少不重復的點。

  • \(1 \le |s| \le 2 \times 10^5\) ,由 L, R, U, D 組成
  • \(1 \le k \le 10^{12}\)

題解

注意到后一次的運動軌跡是前一次的平移,設第一輪執行完 \(s\) 后途徑的點的集合為 \((x_i, y_i)\) ,終點為 \((dx, dy)\) ,那么第 \(n\) 輪執行過程中所經的點即 \((x_i + (n - 1) \times dx, y_i + (n - 1) \times dy)\)

找出第一輪執行所經點的集合中可以通過加減 \((dx, dy)\) 重合的點,那么 \(k\) 次執行過程中這些點即可視為在同一條直線上平移,假設 \(dx, dy > 0\) ,這些點所經的不同點即右上角的點向右上方平移 \(k\) 次和它左下方的點兩兩間的平移。

對於同一直線上的點,可以根據加減 \((dx, dy)\) 得到 \(x > 0\)\(x\) 最小的點為參照點分類。

代碼

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    string s;
    cin >> s;
    long long k;
    cin >> k;
    vector<pair<int, int>> point;
    int dx = 0, dy = 0;
    point.emplace_back(dx, dy);
    for (auto ch : s) {
        if (ch == 'L') {
            --dx;
        } else if (ch == 'R') {
            ++dx;
        } else if (ch == 'U') {
            --dy;
        } else {
            ++dy;
        }
        point.emplace_back(dx, dy);
    }
    sort(point.begin(), point.end());
    point.resize(unique(point.begin(), point.end()) - point.begin());
    if (dx == 0 and dy == 0) {
        cout << point.size() << "\n";
        return 0;
    }
    if (dx == 0) {
        swap(dx, dy);
        for (auto& [x, y]: point) {
            swap(x, y);
        }
    }
    if (dx < 0) {
        dx *= -1;
        for (auto& [x, y]: point) {
            x *= -1;
        }
    }
    map<pair<int, int>, vector<long long>> mp;
    auto normalize = [&](int x, int y) {
        int d = 0;
        if (x >= 0) {
            d = x / dx;
        } else {
            d = -((-x + dx - 1) / dx);
        }
        x -= d * dx;
        y -= d * dy;
        mp[{x, y}].push_back(d);
    };
    for (auto [x, y] : point) {
        normalize(x, y);
    }
    long long ans = 0;
    for (auto [pr, vec] : mp) {
        sort(vec.begin(), vec.end());
        for (int i = 1; i < (int)vec.size(); i++) {
            ans += min(k, vec[i] - vec[i - 1]);
        }
        ans += k;
    }
    cout << ans << "\n";
    return 0;
}

G - Propagation

題意

給出一個有 \(n\) 個結點 \(m\) 條邊的無向簡單圖,第 \(i\) 個結點初始時值為 \(i\) ,給出 \(q\) 次詢問:

  • x :將與結點 \(x\) 相鄰的點賦為 \(x\) 現在的值

輸出最后每個結點的值。

  • \(1 \le n \le 2 \times 10^5\)
  • \(0 \le m \le min(2 \times 10^5, \frac{n(n - 1)}{2})\)
  • \(1 \le q \le 2 \times 10^5\)

題解

直接模擬的復雜度為 \(O_{(qm)}\) ,顯然是不可接受的,考慮如何優化。

根據結點的度數分塊:

  • 若結點 \(x\) 的度數 \(\lt B\) ,即最多有 \(B\) 個結點與它相鄰,那么更新 \(x\) 和相鄰結點,時間復雜度為 \(O_{(B)}\)
  • 若結點 \(x\) 的度數 \(\ge B\) ,為 \(x\) 打上標記 \((val, i)\) ,表示在第 \(i\) 次詢問中與 \(x\) 相鄰的點需要賦為 \(val\) ,時間復雜度為 \(O_{(1)}\)

對於當前結點的更新,顯然當前結點只能被相鄰結點更新:

  • 對於度數 \(\lt B\) 的相鄰結點,已被更新無需遍歷,時間復雜度為 \(O_{(1)}\)

  • 對於度數 \(\ge B\) 的相鄰結點,遍歷尋找最近一次更新,因為這樣的結點最多有 \(\frac{2m}{B}\) 個,所以時間復雜度為 \(O_{(\frac{2m}{B})}\)

此時時間復雜度為 \(O_{(q(B + \frac{2m}{B}))}\) ,易知當 \(B = \sqrt{2m}\) 時取得最小值 \(O_{(q\sqrt{m})}\)

代碼

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m, q;
    cin >> n >> m >> q;
    vector<vector<int>> G1(n);
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        G1[u].push_back(v);
        G1[v].push_back(u);
    }
    const int B = sqrt(2 * m);
    vector<vector<int>> G2(n);
    for (int u = 0; u < n; u++) {
        for (auto v : G1[u]) {
            if ((int)G1[v].size() >= B) {
                G2[u].push_back(v);
            }
        }
    }
    vector<pair<int, int>> ans(n), upd(n);
    for (int i = 0; i < n; i++) {
        ans[i] = upd[i] = {i, -1};
    }
    for (int i = 0; i < q; i++) {
        int x;
        cin >> x;
        --x;
        for (auto v : G2[x]) {
            if (upd[v].second > ans[x].second) {
                ans[x] = upd[v];
            }
        }
        if ((int)G1[x].size() < B) {
            for (auto v : G1[x]) {
                ans[v] = {ans[x].first, i};
            }
        } else {
            upd[x] = {ans[x].first, i};
        }
    }
    for (int i = 0; i < n; i++) {
        for (auto v : G2[i]) {
            if (upd[v].second > ans[i].second) {
                ans[i] = upd[v];
            }
        }
        cout << ans[i].first + 1 << " \n"[i == n - 1];
    }
    return 0;
}

參考

https://atcoder.jp/contests/abc219/editorial/2667

https://atcoder.jp/contests/abc219/editorial/2665

https://atcoder.jp/contests/abc219/editorial/2664

https://atcoder.jp/contests/abc219/submissions/25932859

https://atcoder.jp/contests/abc219/submissions/25937953

https://atcoder.jp/contests/abc219/submissions/25942540


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM