1. 程式人生 > 其它 >AtCoder Beginner Contest 218【A - G】

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);
    long long sub = 0;
    for (auto [w, u, v] : edge) {
        if (dsu.Diff(u, v)) {
            dsu.Union(u, v);
            if (w >= 0) {
                sub += w;
            }
        }
    }
    cout << ans - sub << "\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