AtCoder Beginner Contest 364

~Lanly~發表於2024-07-27

A - Glutton Takahashi (abc364 A)

題目大意

給定\(n\)個字串,問是否有兩個相鄰的 sweet

解題思路

遍歷判斷當前字串與上一個字串是否都為sweet即可。

神奇的程式碼
#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;
    string la;
    cin >> n >> la;
    bool ok = true;
    for (int i = 1; i < n - 1; ++i) {
        string cur;
        cin >> cur;
        if (cur == la && cur.back() == 't')
            ok = false;
        la = cur;
    }
    if (ok)
        cout << "Yes" << '\n';
    else
        cout << "No" << '\n';

    return 0;
}



B - Grid Walk (abc364 B)

題目大意

給定一個二維網格,有障礙物,有空地,給定初始位置。

給定移動操作,若移動目標位置為空地則移動,否則留在原地。

問最終位置。

解題思路

按照題意模擬上下左右移動即可。

神奇的程式碼
#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 h, w;
    int sx, sy;
    cin >> h >> w >> sx >> sy;
    sx--;
    sy--;
    vector<string> s(h);
    for (auto& x : s)
        cin >> x;
    string op;
    cin >> op;
    vector<int> dx = {1, 0, -1, 0};
    vector<int> dy = {0, 1, 0, -1};
    for (auto c : op) {
        int nx, ny;
        if (c == 'L') {
            nx = sx + dx[3];
            ny = sy + dy[3];
        } else if (c == 'R') {
            nx = sx + dx[1];
            ny = sy + dy[1];
        } else if (c == 'U') {
            nx = sx + dx[2];
            ny = sy + dy[2];
        } else {
            nx = sx + dx[0];
            ny = sy + dy[0];
        }
        if (nx < 0 || nx >= h || ny < 0 || ny >= w || s[nx][ny] == '#') {
            continue;
        }
        sx = nx;
        sy = ny;
    }
    cout << sx + 1 << " " << sy + 1 << '\n';

    return 0;
}



C - Minimum Glutton (abc364 C)

題目大意

\(n\)個食物,有鹹度和甜度。安排一個吃的順序,使得吃的食物儘可能少,且一旦鹹度\(> x\)或甜度 \(> y\)就停下來不吃。

解題思路

兩者條件滿足其一即可,那我們就單獨考慮一維,比如第一維\(> x\),那肯定是挑最大的那幾個。因此對第一維排個序,求一遍字首和直到 \(> x\),看看有多少個。

再單獨考慮第二維,挑最大的,看看多少個。

兩個情況取最小值即可。

神奇的程式碼
#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;
    LL x, y;
    cin >> n >> x >> y;
    vector<LL> a(n), b(n);
    for (auto& i : a)
        cin >> i;
    for (auto& i : b)
        cin >> i;
    sort(a.begin(), a.end(), greater<LL>());
    sort(b.begin(), b.end(), greater<LL>());
    auto solve = [](vector<LL>& a, LL x) {
        vector<LL> sum(a.size());
        partial_sum(a.begin(), a.end(), sum.begin());
        int ret = upper_bound(sum.begin(), sum.end(), x) - sum.begin();
        return min(a.size(), ret + 1ul);
    };
    int ans = min(solve(a, x), solve(b, y));
    cout << ans << '\n';

    return 0;
}



D - K-th Nearest (abc364 D)

題目大意

\(n\)個二元組,選擇儘可能少的二元組,使得第一維和 \(> x\),或第二維和 \(> y\)
一維座標,給定\(n\)個點,依次回答 \(q\)個詢問。

每個詢問給定一個位置,問距離該位置第\(k\)近的點的距離是多少。

解題思路

對於詢問的一個位置\(p\),左邊有一些點,右邊也有一些點,對應了一些距離。

如果對這些點的距離進行一個排名,那麼\(p\)往右,點的排名是依次遞增的,同理往左也是依次遞增。

因此可以二分右邊的點,求該點的排名與 \(k\)的關係。而求該點的排名,還要求 \(p\)左邊距離小於它的點的個數,這同樣一個二分就可以解決了。


其實跟求第 \(k\)小的問題一樣,我們可以二分這個距離是\(m\),然後看 \([p-m, p+m]\) 的點的數量,如果是\(\geq k\)則說明該距離是第 \(k\)小或更大距離,則往小的方向收縮。 而統計點的數量則透過兩次二分點座標即可知道。

神奇的程式碼
#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;
    cin >> n >> q;
    vector<int> a(n);
    for (auto& x : a)
        cin >> x;
    sort(a.begin(), a.end());
    auto solve = [&](int b, int k) {
        int l = -1, r = 2e8 + 1;
        while (l + 1 < r) {
            int m = (l + r) / 2;
            int cnt = 0;
            auto lb = lower_bound(a.begin(), a.end(), b - m);
            auto rb = upper_bound(a.begin(), a.end(), b + m);
            cnt = rb - lb;
            if (cnt >= k)
                r = m;
            else
                l = m;
        }
        return r;
    };
    while (q--) {
        int b, k;
        cin >> b >> k;
        int ans = solve(b, k);
        cout << ans << '\n';
    }

    return 0;
}



E - Maximum Glutton (abc364 E)

題目大意

\(n\)個食物,有鹹度和甜度。安排一個吃的順序,使得吃的食物儘可能多,且一旦鹹度\(> x\)或甜度 \(> y\)就停下來不吃。

解題思路

安排順序其實沒什麼用,最終還是決定要吃什麼。

首先考慮滿足鹹度\(\leq x\)甜度 \(\leq y\)的情況下吃的儘可能多。

考慮樸素搜尋,即每個食物吃或不吃,我們需要維護的狀態是 考慮前\(i\)個食物,已經吃的鹹度\(j\),甜度\(k\) 這三個狀態,容易發現這已經足夠作出 吃或不吃的決策,記憶化一下,即\(dp[i][j][k]\)表示考慮前\(i\)個食物,我吃的鹹度為 \(j\),甜度為 \(k\)的最多食物數。

考慮其複雜度,其兩個狀態都是 \(O(10^4)\)的數量級,時間空間都不太行。但注意到其值的取值只有 \(O(n)\),我們可以交換值和狀態的意義,比如設 \(dp[i][j][k]\)表示考慮前\(i\)個食物,我吃了 \(j\)個食物,且鹹度是\(k\)的最小甜度數。

轉移時時刻保證 \(k \leq x\)\(dp[i][j][k] \leq y\)即可,最後再隨便吃一個。這樣狀態數即為 \(O(n^2x)\),轉移為\(O(1)\),總的時間複雜度就是 \(O(n^2x)\)

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

const int inf = 1e9 + 7;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, x, y;
    cin >> n >> x >> y;
    vector<vector<int>> dp(n + 1, vector<int>(x + 1, inf));
    dp[0][0] = 0;
    int ans = 0;
    for (int i = 0; i < n; i++) {
        int a, b;
        cin >> a >> b;
        vector<vector<int>> dp2(n + 1, vector<int>(x + 1, inf));
        for (int j = 0; j <= n; j++) {
            for (int k = 0; k <= x; k++) {
                dp2[j][k] = dp[j][k];
                if (j > 0 && k >= a && dp[j - 1][k - a] + b <= y) {
                    dp2[j][k] = min(dp2[j][k], dp[j - 1][k - a] + b);
                }
            }
        }
        dp.swap(dp2);
    }
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= x; j++) {
            if (dp[i][j] <= y) {
                ans = max(ans, i + 1);
            }
        }
    }
    ans = min(ans, n);
    cout << ans << '\n';

    return 0;
}



F - Range Connect MST (abc364 F)

題目大意

\(n+q\)個點,維護 \(q\)次操作。

\(i\)個操作,連邊 \(j \to n+i\),其中 \(l_i \leq j \leq r_i\),邊權\(c_i\)

問最後是否連通,聯通請求出最小生成樹。

解題思路

是否構成連通塊,即這\(q\)個線段是否構成一個大線段。

考慮最小生成樹怎麼求,即考慮前\(n\)個點該和哪個操作點 \((n+i)\)連邊。

第一感覺就像是線段覆蓋,即從代價小的操作開始,給\(l_i \leq j \leq r_i\)中的每個點合併成一個聯通塊,每合併一次的代價是 \(c_i\)。最後看是否是同個連通塊即可。

連通塊就用並查集維護,合併時總是以編號大的點為根,這樣在上述 從左到右合併時能僅考慮每個連通塊之間,而不用遍歷\(l_i \to r_i\)了。

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

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

    dsu(int _n) : n(_n) {
        p.resize(n);
        sz.resize(n);
        iota(p.begin(), p.end(), 0);
        fill(sz.begin(), sz.end(), 1);
    }

    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 (x > y)
                swap(x, y);
            p[x] = y;
            sz[y] += sz[x];
            return true;
        }
        return false;
    }
};

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, q;
    cin >> n >> q;
    vector<array<int, 3>> seg(q);
    for (auto& [l, r, c] : seg) {
        cin >> l >> r >> c;
        --l, --r;
    }
    sort(seg.begin(), seg.end(),
         [](const array<int, 3>& a, const array<int, 3>& b) {
             return a[2] < b[2];
         });
    dsu d(n);
    auto merge = [&](int l, int r) {
        int cnt = 1;
        int cur = d.get(l);
        while (cur < r) {
            int nxt = d.get(cur + 1);
            d.unite(cur, nxt);
            cur = nxt;
            ++cnt;
        }
        return cnt;
    };
    LL ans = 0;
    for (auto& [l, r, c] : seg) {
        int cnt = merge(l, r);
        ans += 1ll * cnt * c;
    }
    if (d.sz[d.get(0)] != n)
        ans = -1;
    cout << ans << '\n';

    return 0;
}



G - Last Major City (abc364 G)

題目大意

\(n\)個點 \(m\)條邊,邊有邊權。

\(k-1\)個點是重要點。

依次解決以下問題。

分別選定 \(k \to n\) 的節點為重要點,問每個情況下,由重要點構成的最小生成樹的權值。

解題思路

<++>

神奇的程式碼