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\) ,同时为 \(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

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

上一篇:用Solidity语言通过以太坊钱包开发hello world示例


下一篇:在OpenEuler中安装轻量化调试工具CGDB