AtCoder Beginner Contest 366

~Lanly~發表於2024-08-10

A - Election 2 (abc366 A)

題目大意

\(n\)張票,目前投了 \(t\)給高橋, \(a\) 給青木。

問剩餘票隨便分配,是否都是一個結局。

解題思路

考慮最好情況,即剩下票全部投給當前票少的,看看能不能超過對方,會則結局會變,否則不會變。

神奇的程式碼
#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, a;
    cin >> n >> t >> a;
    if (t > a)
        swap(t, a);
    int left = n - t - a;
    if (t + left > a)
        cout << "No" << '\n';
    else
        cout << "Yes" << '\n';

    return 0;
}



B - Vertical Writing (abc366 B)

題目大意

給定\(n\)個字串,把這 \(n\)個字串順時針旋轉 \(90\)度,輸出。

由於字串長度不一,可能會出現 st的情況,前面的 都要替換成*

解題思路

手動旋轉\(90\)度,然後對於每一行,從右往左,一旦碰到字元,後續再碰到 時,替換成 *即可。

神奇的程式碼
#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;
    int m = 0;
    vector<string> s(n);
    for (auto& i : s) {
        cin >> i;
        m = max(m, (int)i.size());
    }
    vector<string> t(m);
    reverse(s.begin(), s.end());
    for (int i = 0; i < m; ++i) {
        for (auto& j : s) {
            if (i < j.size()) {
                t[i] += j[i];
            } else {
                t[i] += ' ';
            }
        }
    }
    for (auto& i : t) {
        bool start = false;
        for (int j = i.size() - 1; j >= 0; --j) {
            if (i[j] != ' ') {
                start = true;
            }
            if (start && i[j] == ' ') {
                i[j] = '*';
            }
        }
    }
    for (auto& i : t)
        cout << i << '\n';

    return 0;
}



C - Balls and Bag Query (abc366 C)

題目大意

\(q\)個操作,分三種。

  • 1 x 放入揹包一個球,數字\(x\)
  • 2 x 從揹包拿出一個球,數字\(x\)
  • 3 問揹包不同數字球的個數

解題思路

map維護一下各個數字球的個數,當\(map[x] = 0\)時移除該元素,詢問就是 \(map.size()\)

神奇的程式碼
#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 q;
    cin >> q;
    map<int, int> cnt;
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            int x;
            cin >> x;
            cnt[x]++;
        } else if (op == 2) {
            int x;
            cin >> x;
            cnt[x]--;
            if (cnt[x] == 0)
                cnt.erase(x);
        } else {
            cout << cnt.size() << '\n';
        }
    }

    return 0;
}



D - Cuboid Sum Query (abc366 D)

題目大意

三維陣列,回答\(q\)個詢問,每個詢問問一個三維區間和。

解題思路

維護一個三元字首和,即可\(O(1)\)透過容斥原理得到一個三元區間的和。

至於如何容斥出來的,感受下二維的情況,三維就是\(2^3\)個項的相加減,公式即在程式碼裡,其實就是\(\sum_{i=0}^{1} \sum_{j=0}^{1} \sum_{k=0}^{1} (-1)^{i+j+k} sum[x-len_x \times i][y-len_y \times j][z- len_z \times k]\)。這裡\(x\)就是 \(rx\)\(x - len_x\)就是 \(lx - 1\),其餘字母同理。

神奇的程式碼
#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<vector<int>>> a(
        n + 1, vector<vector<int>>(n + 1, vector<int>(n + 1)));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            for (int k = 1; k <= n; k++) {
                cin >> a[i][j][k];
            }
        }
    }
    vector<vector<vector<int>>> sum(
        n + 1, vector<vector<int>>(n + 1, vector<int>(n + 1)));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            for (int k = 1; k <= n; k++) {
                sum[i][j][k] = a[i][j][k] + sum[i - 1][j][k] +
                               sum[i][j - 1][k] + sum[i][j][k - 1] -
                               sum[i - 1][j - 1][k] - sum[i - 1][j][k - 1] -
                               sum[i][j - 1][k - 1] + sum[i - 1][j - 1][k - 1];
            }
        }
    }
    int q;
    cin >> q;
    while (q--) {
        int lx, rx, ly, ry, lz, rz;
        cin >> lx >> rx >> ly >> ry >> lz >> rz;
        int ans = sum[rx][ry][rz] - sum[lx - 1][ry][rz] - sum[rx][ly - 1][rz] -
                  sum[rx][ry][lz - 1] + sum[lx - 1][ly - 1][rz] +
                  sum[lx - 1][ry][lz - 1] + sum[rx][ly - 1][lz - 1] -
                  sum[lx - 1][ly - 1][lz - 1];
        cout << ans << '\n';
    }

    return 0;
}



E - Manhattan Multifocal Ellipse (abc366 E)

題目大意

二維平面。給定\(n\)個點\((x_i, y_i)\)

給定\(D\),問有多少個座標 \((x,y)\),滿足\(\sum_{i=1}^{n}(|x - x_i| + |y - y_i|) \leq D\)

解題思路

從求和式子可以看出\(x,y\)是相互獨立的,\(\sum_{i=1}^{n}(|x - x_i| + |y - y_i|) = \sum_{i=1}^{n}|x - x_i| + \sum_{i=1}^{n}|y - y_i|) \leq D\),我們可以分別考慮 \(x,y\)軸的情況。

注意到點座標範圍只有 \([-10^6, 10^6]\) ,我們可以直接列舉\(x\)\(y\)的值,由於\(D \leq 10^6\),可以粗略算出來\(x,y\)的範圍不會超過\([-2e6,2e6]\)

因此,我們直接列舉每個\(x\)\(-2e6\)\(2e6\),計算得到 \(\sum_{i=1}^{n}|x-x_i|\)的值 。而計算這個值可以\(O(1)\)\(O(\log n)\)算出來,即把絕對值去掉,即\(\sum_{x_i < x} (x - x_i) + \sum_{x_i \geq x}(x_i - x)\)。我們對\(x_i\)排序,然後可以二分\(x\)或者列舉\(x\)時動態維護這個邊界點,同時維護字首和presum字尾和sufsum,以及邊界點前面的點數\(it\),那麼 \(\sum_{x_i < x} (x - x_i) + \sum_{x_i \geq x}(x_i - x) = it * x - presum + sufsum - (n - it) * x\)

這樣就算出了每個\(x\)\(\sum_{i = 1}^{n}|x - x_i|\),同理計算出每個 \(y\)\(\sum_{i=1}^{n}|y-y_i|\)

剩下的問題就是從\(x\)裡選一個 \(\sum_x\),從 \(y\)裡選一個 \(\sum_y\) ,有\(\sum_x + \sum_y \leq D\),有多少對。

這就是個經典問題,先對兩個 \(\sum\)排序,然後依次列舉 \(\sum_x\),那麼 \(\sum_y \leq D - sum_1\)的都是滿足的,可以二分這個邊界點,或者雙指標維護一下得到滿足條件的\(\sum_y\)的數量,累計即為答案。

神奇的程式碼
#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, d;
    cin >> n >> d;
    vector<int> x(n), y(n);
    for (int i = 0; i < n; ++i)
        cin >> x[i] >> y[i];
    auto solve = [&](vector<int>& a) {
        sort(a.begin(), a.end());
        vector<LL> dis;
        int up = 2e6;
        int it = 0;
        LL presum = 0, sufsum = accumulate(a.begin(), a.end(), 0ll);
        for (int i = -up; i <= up; ++i) {
            LL sum = 0;
            while (it < n && a[it] < i) {
                presum += a[it];
                sufsum -= a[it];
                ++it;
            }
            sum = 1ll * it * i - presum + sufsum - 1ll * (n - it) * i;
            dis.push_back(sum);
        }
        sort(dis.begin(), dis.end());
        return dis;
    };
    auto dis1 = solve(x);
    auto dis2 = solve(y);
    LL ans = 0;
    int it = dis2.size() - 1;
    for (auto i : dis1) {
        while (it >= 0 && dis2[it] + i > d)
            --it;
        ans += it + 1;
    }
    cout << ans << '\n';

    return 0;
}



F - Maximum Composition (abc366 F)

題目大意

給定\(n\)個一次函式 \(f_i(x) = a_ix + b_i\)

選擇 \(k(\leq 10)\)個不同的一次函式,使得 \(f_{p_1}(f_{p_2}(...f_{p_k}(1)))\)最大。

解題思路

注意到\(a_i > 0, b_i > 0\),如果允許 重複選函式的話,因為\(x\)越大, \(f(x)\)越大,因此每次肯定選,使得\(f_i(x)\)值最大的函式\(f_i\) 。但這裡不允許重複。

不允許重複,會產生什麼問題呢?比如一個函式\(f_1(x) = 10x + 1\) ,另一個函式\(f_2(x) = x+9\),如果按照上述方法,結果就是\(f_2(f_1(1))=f_2(11) = 20\),然而反過來則是 \(f_1(f_2(1)) = f_1(10)=101\)。即函式 \(f_1\)雖然在第一步使用,可以得到最大的 \(f\),但後使用,可以變得更大。因此,如果最後我會選\(f_1\)\(f_2\)\(f_2\)會優先使用,\(f_1\)會後使用。

這裡就出現了函式之間,選擇的偏序(順序)問題。這個偏序怎麼定義呢?函式\(f_i,f_j\) ,如果\(f_i(f_j(x)) > f_j(f_i(x))\),則有 \(a_i(a_jx + b_j) + b_i\geq a_j(a_ix + b_i) + b_j\),我們把 \(i,j\)分離在一左一右,得到 \(\frac{a_i - 1}{b_i} \geq \frac{a_j - 1}{b_j}\)

這意味著說,如果\(\frac{a_i - 1}{b_i} \geq \frac{a_j - 1}{b_j}\),則\(f_i(f_j(x)) > f_j(f_i(x))\),即我先用\(f_j\),再用 \(f_i\)

有了這個函式使用的偏序有什麼用呢?題目要使值最大,不僅要考慮選哪 \(k\)個函式,還要考慮這 \(k\)個函式複合的順序。而現在我們已經有了一個最優複合順序了,那剩下的問題就是選哪 \(k\)個函式。這其實就是一個選或不選的揹包問題了。

先對 \(f_i\)按照 \(\frac{a_i - 1}{b_i}\)從小到大排序,然後設\(dp[i][j]\)表示考慮前 \(i\)個函式,已經使用了 \(j\)個函式的最大函式值。轉移則考慮當前函式選或不選,從\(dp[i - 1][j-1]\)\(dp[i-1][j]\)轉移即可。初始條件 \(dp[0][0] = 1\)

神奇的程式碼
#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, k;
    cin >> n >> k;
    vector<array<int, 2>> f(n);
    for (auto& [a, b] : f)
        cin >> a >> b;
    sort(f.begin(), f.end(),
         [](const array<int, 2>& a, const array<int, 2>& b) {
             auto& [a1, b1] = a;
             auto& [a2, b2] = b;
             return (a2 - 1) * b1 > (a1 - 1) * b2;
         });
    vector<LL> dp(k + 1, 0);
    dp[0] = 1;
    for (int i = 0; i < n; ++i) {
        auto& [a, b] = f[i];
        vector<LL> dp2 = dp;
        for (int j = 1; j <= k; ++j) {
            dp2[j] = max(dp2[j], a * dp[j - 1] + b);
        }
        dp.swap(dp2);
    }
    cout << dp[k] << '\n';

    return 0;
}



G - XOR Neighbors (abc366 G)

題目大意

給定一張圖,給每個點一個點權,使得每個點的鄰居的點權異或和為\(0\)。給出方案,或告知不能。

解題思路

<++>

神奇的程式碼