AtCoder Beginner Contest 218【A - G】

比赛链接:https://atcoder.jp/contests/abc218/tasks

A - Weather Forecast

题意

判断一个字符串的第 \(n\) 个字符是否为 o

题解

模拟。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    string s;
    cin >> s;
    cout << (s[n - 1] == 'o' ? "Yes" : "No") << "\n";
    return 0;
}

B - qwerty

题意

依次输出第 \(x_i\) 个小写字母。

题解

模拟。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    for (int i = 0; i < 26; i++) {
        int x;
        cin >> x;
        cout << char('a' + x - 1);
    }
    return 0;
}

C - Shapes

题意

给出两个 \(n \times n\) 的图形,判断能否通过旋转(每次九十度)、平移使得二者重合。

  • \(1 \le n \le 200\)

题解

枚举旋转的次数,若二者可以平移至重合则所有横纵坐标之差相等。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<string> MP1(n), MP2(n);
    for (int i = 0; i < n; i++) {
        cin >> MP1[i];
    }
    for (int i = 0; i < n; i++) {
        cin >> MP2[i];
    }
    auto rotate = [](int n, vector<string>& MP) { // n x n 90 degree clockwise
        auto t_MP(MP);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                t_MP[j][n - 1 - i] = MP[i][j];
            }
        }
        MP = t_MP;
    };
    bool ok = false;
    for (int t = 0; t < 4; t++) {
        rotate(n, MP1);
        vector<int> x1, y1, x2, y2;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (MP1[i][j] == '#') {
                    x1.push_back(i), y1.push_back(j);
                }
                if (MP2[i][j] == '#') {
                    x2.push_back(i), y2.push_back(j);
                }
            }
        }
        if (x1.size() != x2.size()) {
            continue;
        }
        bool trans = true;
        for (int i = 0; i < (int)x1.size(); i++) {
            if (x1[i] - x2[i] != x1[0] - x2[0] or y1[i] - y2[i] != y1[0] - y2[0]) {
                trans = false;
            }
        }
        if (trans) {
            ok = true;
        }
    }
    cout << (ok ? "Yes" : "No") << "\n";
    return 0;
}

D - Rectangles

题意

给出平面上 \(n\) 个不等的点,判断可以形成多少与横纵坐标轴平行的矩形。

  • \(4 \le n \le 2000\)

题解

枚举对角顶点即可,同一个矩形会被主副对角线枚举两次,所以最终答案还要再除以二。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<int> x(n), y(n);
    map<pair<int, int>, bool> mp;
    for (int i = 0; i < n; i++) {
        cin >> x[i] >> y[i];
        mp[{x[i], y[i]}] = true;
    }
    int ans = 0;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (x[i] == x[j] or y[i] == y[j]) {
                continue;
            }
            if (mp[{x[i], y[j]}] and mp[{x[j], y[i]}]) {
                ++ans;
            }
        }
    }
    cout << ans / 2 << "\n";
    return 0;
}

E - Destruction

题意

给出一个 \(n\) 个点 \(m\) 条边的无向连通图,每次可以移除一条边,收益为该边的权值 \(c_i\)

问在保证图连通的情况下,最大收益为多少。

  • \(2 \le n \le 2 \times 10^5\)

  • \(n - 1 \le m \le 2 \times 10^5\)

  • \(-10^9 \le c_i \le 10^9\)

题解

类似最小生成树的思想,只不过在计算收益时只考虑非负权边。

代码

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

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 main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<tuple<int, int, int>> edge(m);
    long long ans = 0;
    for (auto& [w, u, v] : edge) {
        cin >> u >> v >> w;
        --u, --v;
        if (w >= 0) {
            ans += w;
        }
    }
    sort(edge.begin(), edge.end());
    dsu dsu(n);
    for (auto [w, u, v] : edge) {
        if (dsu.Diff(u, v)) {
            dsu.Union(u, v);
            if (w >= 0) {
                ans -= w;
            }
        }
    }
    cout << ans << "\n";
    return 0;
}

F - Blocked Roads

题意

给出一个 \(n\) 个点 \(m\) 条边的有向图,问如果移除第 \(i\) 条边,结点 \(1\) 与结点 \(n\) 的最短距离为多少。

  • \(2 \le n \le 400\)
  • \(1 \le m \le n(n - 1)\)

题解

\(Dijkstra\)\(O_{(n^2)}\)\(O_{((n + m)log_n)}\) 两种实现方法,本题中结点数 \(n\) 较少,所以可以先确定一条从 \(1\)\(n\) 的最短路径,之后若第 \(i\) 条边不在此路径中,直接输出 \(dis_{(1, n)}\) 即可,否则移除该边后重跑一遍 \(O_{(n^2)}\)\(Dijkstra\) ,因为路径中最多含有 \(n - 1\) 条边,所以最坏时间复杂度为 \(O_{(n^3)}\)

Tips

移除边后记得回溯。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> edge(n, vector<int> (n, 1e9));
    vector<int> u(m), v(m);
    for (int i = 0; i < m; i++) {
        cin >> u[i] >> v[i];
        --u[i], --v[i];
        edge[u[i]][v[i]] = 1;
    }
    vector<int> dis(n), pre(n, -1);
    auto Dijkstra = [&]() {
        fill(dis.begin(), dis.end(), 1e9);
        dis[0] = 0;
        queue<int> que;
        que.push(0);
        vector<bool> vis(n);
        vis[0] = true;
        while (not que.empty()) {
            int u = que.front();
            que.pop();
            for (int v = 0; v < n; v++) {
                if (not vis[v] and dis[u] + edge[u][v] < dis[v]) {
                    dis[v] = dis[u] + edge[u][v];
                    vis[v] = true;
                    pre[v] = u;
                    que.push(v);
                }
            }
        }
    };
    Dijkstra();
    vector<vector<bool>> used(n, vector<bool> (n));
    for (int u = pre[n - 1], v = n - 1; u != -1; v = u, u = pre[u]) {
        used[u][v] = true;
    }
    for (int i = 0; i < m; i++) {
        if (used[u[i]][v[i]]) {
            edge[u[i]][v[i]] = 1e9;
            Dijkstra();
            cout << (dis[n - 1] == 1e9 ? -1 : dis[n - 1]) << "\n";
            edge[u[i]][v[i]] = 1;
            Dijkstra();
        } else {
            cout << (dis[n - 1] == 1e9 ? -1 : dis[n - 1]) << "\n";
        }
    }
    return 0;
}

G - Game on Tree 2

题意

给出一棵有 \(n\) 个结点的树,每个结点的权值为 \(a_i\)

初始时结点 \(1\) 处有一枚棋子,Alice 先手,Bob 后手,每次可以将棋子从当前结点移至任一未访问过的相邻结点。

最终收益为所经结点权值 multiset 的中位数,Alice 想要将其最大化,Bob 想要将其最小化,双方均采取最优策略,问最终集合的中位数会是多少。

中位数的定义 若集合大小为奇数,则为中间的数,否则为中间两个数的平均值。
  • \(2 \le n \le 10^5\)
  • \(2 \le a_i \le 10^9\)\(a_i\) 均为偶数

题解

假设结点 \(1\) 为根节点,那么每个叶结点的中位数都是唯一确定的,同时可以由深度确定本轮操作者,利用 \(dfs\) + 回溯 进行树上 \(dp\) 即可。

更新、询问集合的中位数可以用 坐标压缩 + 权值树状数组,也可以用两个 multiset 分别保存前后一半的值。

代码

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

struct Kth_multiset {
    multiset<int> mst1, mst2; // always mst2.size() == mst1.size or mst2.size() == mst1.size + 1   for example: mst1:{2, 4} mst2:{4, 6, 8}

    void balance() {
        if (mst2.size()) {
            mst1.insert(*mst2.begin());
            mst2.erase(mst2.begin());
        }
        while (mst1.size() > mst2.size()) {
            mst2.insert(*mst1.rbegin());
            mst1.erase(prev(mst1.end()));
        }
    }

    void insert(int val) { // all vals should be even
        mst2.insert(val);
        balance();
    }

    void erase(int val) {
        if (mst2.find(val) != mst2.end()) {
            mst2.erase(mst2.find(val));
        } else {
            mst1.erase(mst1.find(val));
        }
        balance();
    }

    int find_kth() {
        if (mst2.size() == mst1.size() + 1) {
            return *mst2.begin();
        } else {
            return (*mst1.rbegin() + *mst2.begin()) / 2; // because of here
        }
    }
} K;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    vector<vector<int>> G(n);
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    vector<int> dp(n);
    function<void(int, int, int)> dfs = [&](int u, int p, int d) {
        K.insert(a[u]);
        int mi = 2e9, mx = 0;
        for (auto v : G[u]) {
            if (v != p) {
                dfs(v, u, d + 1);
                mi = min(mi, dp[v]), mx = max(mx, dp[v]);
            }
        }
        if (mx == 0) { // is leaf
            dp[u] = K.find_kth();
        } else { 
            dp[u] = d & 1 ? mi : mx;
        }
        K.erase(a[u]);
    };
    dfs(0, -1, 0);
    cout << dp[0] << "\n";
    return 0;
}

参考

https://atcoder.jp/contests/abc218/submissions/25764499

https://atcoder.jp/contests/abc218/editorial/2631

posted @ 2021-09-13 16:15  Kanoon  阅读(214)  评论(2编辑  收藏  举报