AtCoder Beginner Contest 360

~Lanly~發表於2024-06-30

A - A Healthy Breakfast (abc360 A)

題目大意

給定一個字串包含RMS,問R是否在S的左邊。

解題思路

比較RS的下標,誰小即誰在左邊。

神奇的程式碼
#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);
    string s;
    cin >> s;
    cout << (s.find('R') < s.find('M') ? "Yes" : "No") << '\n';

    return 0;
}



B - Vertical Reading (abc360 B)

題目大意

給定兩個字串\(s,t\),找到一個 \(1 \leq c \leq w < |s|\),使得將 \(s\)拆開,每個子串有\(w\)個字母, 對每個子串取第\(c\)位字母出來(不存在則不取),使其組成 \(t\)

解題思路

由於\(|s| \leq 100\),直接花 \(O(|s|^2)\)列舉 \(c,w\),然後拆開子串、拼接、比較即可。時間複雜度是 \(O(n^3)\)

神奇的程式碼
#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);
    string s, t;
    cin >> s >> t;
    bool ok = false;
    for (int i = 1; i < s.size(); i++) {
        vector<string> sub;
        for (int j = 0; j < s.size(); j += i) {
            sub.push_back(s.substr(j, i));
        }
        for (int j = 0; j < i; ++j) {
            string ans;
            for (auto& x : sub) {
                if (j < x.size())
                    ans += x[j];
            }
            if (ans == t) {
                ok = true;
            }
        }
    }
    if (ok)
        cout << "Yes" << '\n';
    else
        cout << "No" << '\n';

    return 0;
}



C - Move It (abc360 C)

題目大意

給定 \(n\)個箱子。再給定\(n\)個球所在的箱子位置,球有重量。

移動球到其他箱子裡,代價是球的重量。

求最小的代價,使得每個箱子各有一個球。

解題思路

對於一個有多個球的盒子,因為最終會有一個球留在盒子裡,為了最小代價,那最重的球不移動,而移動其他的球。

因此最終的代價就是每個盒子除最重球的球重量的和。

神奇的程式碼
#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;
    cin >> n;
    vector<vector<int>> box(n);
    vector<int> pos(n);
    for (auto& i : pos)
        cin >> i;
    for (int i = 0; i < n; ++i) {
        int w;
        cin >> w;
        box[pos[i] - 1].push_back(w);
    }
    int ans = 0;
    for (auto& i : box) {
        if (i.empty())
            continue;
        sort(i.begin(), i.end(), greater<int>());
        ans += accumulate(i.begin(), i.end(), 0) - i[0];
    }
    cout << ans << '\n';

    return 0;
}



D - Ghost Ants (abc360 D)

題目大意

一維數軸,螞蟻移動,有方向,速度一樣。碰撞時不改變方向,繼續保持原方向行走。

經過\(t\)時刻後,問碰撞的螞蟻對數。

解題思路

同方向的螞蟻不會碰撞,因此僅考慮不同向的。

考慮往右的螞蟻會與哪些往左的螞蟻碰撞。

由於所有螞蟻速度一樣,因此由相向運動得知,假設當前往右的螞蟻位置為\(x\),那麼所有往左的位於\([x, x + 2t]\)的螞蟻都會與其相撞。

根據螞蟻前進方向對座標排序,二分一下就能找到往左的位於 \([x, x + 2t]\)的螞蟻數量。

所有數量累加即為答案。時間複雜度是 \(O(n \log n)\)

神奇的程式碼
#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, t;
    string dir;
    cin >> n >> t >> dir;
    array<vector<int>, 2> pos;
    for (int i = 0; i < n; ++i) {
        int p;
        cin >> p;
        pos[dir[i] - '0'].push_back(p);
    }
    for (auto& x : pos)
        sort(x.begin(), x.end());
    LL ans = 0;
    for (int i = 1; i < 2; ++i) {
        auto& cur = pos[i];
        auto& nxt = pos[i ^ 1];
        for (auto x : cur) {
            LL l = x;
            LL r = x + t * (i == 1 ? 1 : -1) * 2ll;
            if (l > r)
                swap(l, r);
            auto ll = lower_bound(nxt.begin(), nxt.end(), l);
            auto rr = upper_bound(nxt.begin(), nxt.end(), r);
            ans += rr - ll;
        }
    }
    cout << ans << '\n';

    return 0;
}



E - Random Swaps of Balls (abc360 E)

題目大意

\(n\)個球,其中第一個是黑球,其餘是白球。

進行以下操作 \(k\)次:

  • 隨機兩次獨立選取 \(i,j\),交換第 \(i\)個球和第 \(j\)個球。

問黑球位置的期望值。

解題思路

假設最終黑球位於第\(i\)個球的機率是\(p_i\),根據期望定義,答案就是 \(\sum_{i=1}^{n} i p_i\)。考慮如何求\(p_i\)

容易發現第 \(2,3,...,n\)個球沒有本質區別,其機率都是一樣的,因此最終我們需要求的就兩個數

  • \(p_1\)即位於第一個球的機率
  • \(p_2\)即位於非第一個球的機率

容易發現經過第\(k\)次操作的機率,僅依賴於第 \(k-1\)次的情況,因此可以迭代球,即設 \(dp[k][0/1]\)表示經過 \(k\)次操作後,球位於第一個球/不位於第一個球的機率。

轉移就考慮本次操作是否將球移動到第一個球或非第一個球。設移動機率\(move = 2\frac{1}{n}\frac{n-1}{n}\),不移動機率\(stay = 1 - move\),移動到某一個位置的機率為 \(move1 = \frac{move}{n-1} = \frac{2}{n^2}\)

\(dp[i][0] = dp[i - 1][0] \times stay + dp[i - 1][1] \times move1\),不動或者從非一球移動到一球。
\(dp[i][1] = dp[i - 1][0] \times move + dp[i - 1][1] \times (1 - move1)\),從一球移動或者從非一球移動到非移動(全機率-移動到一球的機率)

最後\(p_1 = dp[k][0],p_2 = p_3 = ... = p_n = \frac{dp[k][1]}{n-1}\),答案就是\(\sum_{i=1}^{n} i p_i = dp[k][0] + \frac{(n+2)(n-1)}{2} \frac{dp[k][1]}{n-1}\)

時間複雜度為\(O(k)\)

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

const int mo = 998244353;

long long qpower(long long a, long long b) {
    long long qwq = 1;
    while (b) {
        if (b & 1)
            qwq = qwq * a % mo;
        a = a * a % mo;
        b >>= 1;
    }
    return qwq;
}

long long inv(long long x) { return qpower(x, mo - 2); }

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, k;
    cin >> n >> k;
    int invn = inv(n);
    int invn1 = inv(n - 1);
    int move = 2ll * invn * (n - 1) % mo * invn % mo;
    int stay = (1ll - move + mo) % mo;
    int move1 = 1ll * move * invn1 % mo;
    array<int, 2> dp{};
    dp[0] = 1;
    for (int i = 0; i < k; i++) {
        array<int, 2> dp2{};
        dp2[0] = (1ll * dp[0] * stay % mo + 1ll * dp[1] * move1 % mo) % mo;
        dp2[1] = (1ll * dp[0] * move % mo +
                  1ll * dp[1] * ((1 - move1 + mo) % mo) % mo) %
                 mo;
        dp.swap(dp2);
    }
    int ans =
        (dp[0] + 1ll * (n + 2) * (n - 1) / 2 % mo * dp[1] % mo * invn1 % mo) %
        mo;
    cout << ans << '\n';

    return 0;
}



F - InterSections (abc360 F)

題目大意

給定\(n\)個區間,若倆區間\([l_a, r_a],[l_b,r_b]\)有交叉 ,則有\(l_a < l_b < r_a < r_b\)

\(f(l,r)\)表示與 \([l,r]\)有交叉的區間數。

求最大值。

解題思路

<++>

神奇的程式碼



G - Suitable Edit for LIS (abc360 G)

題目大意

給定一個陣列,進行一次操作,使得最長上升子序列的長度最大。

操作為,將任意一個數改為任意一個數。

解題思路

這種有一次修改的,可以從修改處考慮,其修改處左右兩邊是一個正常的最長上升子序列問題。

因此事先求出\(pre[i]\)表示從左到右,選了第 \(i\)個數字的最長上升子序列長度,\(suf[i]\)表示從右到左,選了第 \(i\)個數字的最長下降子序列長度,需要使用\(O(n\log n)\)的方法。考慮如何將 \(pre\)\(suf\)組合。

注意到是嚴格上升,如果列舉 \(suf_i\),思考 \(\max(pre_j + suf_i + 1)\)有什麼條件,以及 \(j\)如何找。

列舉 \(i\),考慮 \(j\),則有 \(k \in (j, i), a_k\)需要修改,因此 \(j < i - 1\),同時由於嚴格遞增, \(a_i > a_j + 1\) ,這樣\(a_k\)才能更改成對應的數,使得 \(pre_j\)\(suf_i\)拼接起來得到一個更長的最長上升子序列。

即需要解決這麼一個問題\(\max_{j < i - 1 \& a_i > a_j + 1} (pre_j + suf_i + 1)\)。容易發現這就是一個樸素的二維偏序問題,用線段樹求解即可。注意需要對\(a_i\)離散化。

\(O(n \log n)\)的一種最長上升子序列的求法,即\(pos[i]\)表示最長上升子序列長度為 \(i\)的末尾數字(最大數字)的最小值。注意到 \(pos\)是一個遞增的陣列,因此對於當前數字\(a_i\) ,可以二分找到它可以接在哪個數字\(a_j\)的後面,進而知道了當前的\(dp[i]\),然後更新 \(pos\)陣列。或者樸素的 \(O(n^2)\)\(dp\),分析轉移式可以注意到也是一個二維偏序問題,也可以用線段樹解決。

用線段樹解二維偏序,下標是\(a_j\),值是\(dp[j]\)。即當前\(dp[j]\)求出來後,將 \(dp[j - 1]\)插入到線段樹裡,即將 \(a_j\)位置的值賦予 \(dp[j]\)

這樣在求\(dp[i]\)時,線段樹裡的值都是 \(j < i - 1\)\(dp[j]\)的值,自然滿足第一個 \(j < i - 1\)的條件,因為線段樹的下標是 \(a_j\),而 \(a_i > a_j + 1\)相當於一個區間 \([1, a_i - 2]\),要求的就是這個區間的\(dp\)最大值,因為線段樹維護的就是\(dp[j]\),故區間查詢最大值、單點修改即可。而由於\(a_i\)很大,為作為線段樹的下標,需要離散化。

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

const int inf = 1e9 + 8;

const int N = 2e5 + 8;

class segment {
#define lson (root << 1)
#define rson (root << 1 | 1)
  public:
    int maxx[N << 2];

    void build(int root, int l, int r) {
        if (l == r) {
            maxx[root] = 0;
            return;
        }
        int mid = (l + r) >> 1;
        build(lson, l, mid);
        build(rson, mid + 1, r);
        maxx[root] = max(maxx[lson], maxx[rson]);
    }

    void update(int root, int l, int r, int pos, int val) {
        if (l == r) {
            maxx[root] = max(maxx[root], val);
            return;
        }
        int mid = (l + r) >> 1;
        if (pos <= mid)
            update(lson, l, mid, pos, val);
        else
            update(rson, mid + 1, r, pos, val);
        maxx[root] = max(maxx[lson], maxx[rson]);
    }

    LL query(int root, int l, int r, int L, int R) {
        if (L <= l && r <= R) {
            return maxx[root];
        }
        int mid = (l + r) >> 1;
        int resl = -inf, resr = -inf;
        if (L <= mid)
            resl = query(lson, l, mid, L, R);
        if (R > mid)
            resr = query(rson, mid + 1, r, L, R);
        return max(resl, resr);
    }
} sg;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<int> a(n);
    for (auto& x : a)
        cin >> x;
    auto solve = [&](vector<int>& a) -> vector<int> {
        vector<int> dp(n, 1);
        vector<int> pos(n + 1, inf);
        pos[0] = -inf;
        for (int i = 0; i < n; i++) {
            auto it = lower_bound(pos.begin(), pos.end(), a[i]) - pos.begin();
            dp[i] = it;
            pos[it] = min(pos[it], a[i]);
        }
        return dp;
    };
    auto pre = solve(a);
    reverse(a.begin(), a.end());
    transform(a.begin(), a.end(), a.begin(), [](int x) { return -x; });
    auto suf = solve(a);
    reverse(suf.begin(), suf.end());
    reverse(a.begin(), a.end());
    transform(a.begin(), a.end(), a.begin(), [](int x) { return -x; });

    vector<int> b(a.begin(), a.end());
    b.push_back(-inf);
    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());
    auto rank = [&](int x) {
        return lower_bound(b.begin(), b.end(), x) - b.begin() + 1;
    };
    int ans = 0;
    int m = b.size();
    sg.build(1, 1, m);
    for (int i = 0; i < n; ++i) {
        int cnt = sg.query(1, 1, m, 1, rank(a[i] - 1) - 1);
        ans = max(ans, suf[i] + cnt + (i != 0));
        if (i > 0)
            sg.update(1, 1, m, rank(a[i - 1]), pre[i - 1]);
    }
    ans = max(ans, sg.query(1, 1, m, 1, m) + 1);
    cout << ans << '\n';

    return 0;
}