AtCoder Beginner Contest 365

~Lanly~發表於2024-08-04

A - Leap Year (abc365 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 a;
    cin >> a;
    int day = 365;
    if (a % 400 == 0 || (a % 4 == 0 && a % 100 != 0)) {
        day = 366;
    }
    cout << day << '\n';

    return 0;
}



B - Second Best (abc365 B)

題目大意

給定\(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<int> a(n);
    for (auto& x : a)
        cin >> x;
    vector<int> id(n);
    iota(id.begin(), id.end(), 0);
    sort(id.begin(), id.end(), [&](int i, int j) { return a[i] < a[j]; });
    cout << id[n - 2] + 1 << '\n';

    return 0;
}



C - Transportation Expenses (abc365 C)

題目大意

給定\(n\)個人的交通費用\(a_i\),確定最大補貼額度 \(x\),使得給每個人的補貼為 \(\min(x, a_i)\), 總補貼額度不超過\(m\)

解題思路

很顯然,\(x\)越高,總補貼額度越高,\(x\)越低 ,則補貼額度就越低,兩者呈單調性。

因此可以二分\(x\),然後計算補貼額度。計算補貼額度就直接\(for\)迴圈,求一遍 \(\sum \min(x, a_i)\)即可。

神奇的程式碼
#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 m;
    cin >> n >> m;
    vector<LL> a(n);
    for (auto& i : a)
        cin >> i;
    LL l = 0, r = m + 1;
    if (accumulate(a.begin(), a.end(), 0LL) <= m) {
        cout << "infinite" << '\n';
    } else {
        auto check = [&](LL x) {
            LL sum = 0;
            for (auto i : a)
                sum += min(i, x);
            return sum <= m;
        };
        while (l + 1 < r) {
            LL mid = (l + r) / 2;
            if (check(mid))
                l = mid;
            else
                r = mid;
        }
        cout << l << '\n';
    }

    return 0;
}



以下程式碼不是二分,先假定\(x\)的取值是 \(a_i\)之中,對 \(a_i\)從小到大遍歷,找到最大的,總補貼度不超過 \(m\)\(a_i\),但真正的\(x\)不一定在 \(a_i\)之中,因為此時還有一些多餘的錢,還可以分給每個人,只是達不到\(a_{i+1}\),因此剩餘錢再均分給剩餘人,得到真正的 \(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 m;
    cin >> n >> m;
    vector<int> a(n);
    for (auto& x : a)
        cin >> x;
    sort(a.begin(), a.end());
    LL tot = accumulate(a.begin(), a.end(), 0LL);
    if (tot <= m) {
        cout << "infinite" << '\n';
    } else {
        int id = -1;
        LL remain = 0;
        LL presum = 0;
        for (int i = 0; i < n; ++i) {
            LL sum = presum + 1ll * a[i] * (n - i);
            if (sum <= m) {
                id = i;
                remain = m - sum;
            }
            presum += a[i];
        }
        LL ans = m / n;
        if (id != -1) {
            ans = a[id] + remain / (n - id - 1);
        }
        cout << ans << '\n';
    }

    return 0;
}



D - AtCoder Janken 3 (abc365 D)

題目大意

剪刀石頭布。

給定青木\(n\)局的操作,要求確定高橋對應的操作,要求:

  • 每一局高橋要麼贏,要麼平手。
  • 高橋不能連續兩局出同樣的手勢。

最大化高橋贏的局數。

解題思路

考慮某一局高橋能做出的手勢,取決於兩個

  • 青木此局的手勢,決定了高橋不能輸
  • 高橋上一局的手勢,確保不會連續兩局出同樣的手勢

由此,為了能夠作出決策,我們僅需知道第\(i\)局和上一局出的手勢 \(j\)即可。

\(dp[i][j]\)表示考慮前 \(i\)局, 第\(i\)局高橋的手勢是 \(j\),高橋贏的局數的最大值。

轉移則考慮高橋當前是贏還是平手(手勢不與上一局相同 ),從\(dp[i-1][*]\)轉移過來即可。

神奇的程式碼
#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;
    string s;
    cin >> n >> s;
    map<char, int> tr{{'R', 0},
                      {'P', 1},
                      {'S', 2}};
    string t = "PSR";
    array<int, 3> dp{0, 0, 0};
    for (auto& c : s) {
        array<int, 3> dp2{-inf, -inf, -inf};
        int pid = tr[c];
        char hashi = t[pid];
        int id = tr[hashi];
        for (int i = 0; i < 3; i++) {
            if (i != id)
                dp2[id] = max(dp2[id], dp[i] + 1);
            if (i != pid)
                dp2[pid] = max(dp2[pid], dp[i]);
        }
        dp2.swap(dp);
    }
    int ans = *max_element(dp.begin(), dp.end());
    cout << ans << '\n';

    return 0;
}



E - Xor Sigma Problem (abc365 E)

題目大意

給定\(n\)個數 \(a_i\),求\(\sum_{i=1}^{n-1}\sum_{j=i+1}^{n} a_i \oplus a_{i+1} \oplus ... \oplus a_j\)

\(\oplus\)是異或操作。

解題思路

樸素做法顯而易見的\(O(n^2)\)

由於異或操作下,二進位制下每一位是相互獨立的,因此我們可以考慮每一位的貢獻。即考慮\(2^0,2^1,2^2,...,2^31\)在這 \(O(n^2)\)個異或表示式結果中出現的次數。

\(\sum_{i=1}^{n-1}\sum_{j=i+1}^{n} a_i \oplus a_{i+1} \oplus ... \oplus a_j = \sum_{i=0}^{31} 2^i cnt_i\)

考慮如何求\(cnt_i\)

單獨考慮每個數二進位制下的第\(k\)位,如果 \(2^k\)在一個\(a_i \oplus ... \oplus a_j\)出現過,則這說明這些數的二進位制下,第\(k\)位是 \(1\)的次數是奇數。

因此對於 \(k\),我們求 \(odd_i\)表示 \(a_i\)在二進位制下的第 \(k\)位是不是 \(1\)。然後考慮有多少個區間\(odd\)和是奇數。

區間異或和區間加法一樣,可以表示成兩個字首和相減。因此求\(odd_i\)的字首和 \(presum_i = odd_1 \oplus odd_2 \oplus ... \oplus odd_i\) ,則\(odd_i \oplus odd_{i+1} \oplus ... \oplus odd_j = presum_j \oplus presum_{i-1}\)

因此我們要求有多少對 \((i,j)\),滿足 \(presum_{j} \oplus presum_{i-1} = 1\),我們可以列舉\(j\),然後對應的 \(i\)的數量。

  • 如果 \(presum_j = 1\),則 \(presum_{i-1} = 0\)
  • 如果 \(presum_j = 0\),則 \(presum_{i-1} = 1\)
    因此我們只需在從小到大列舉\(j\)時, 實時維護\(presum_i = 0\)\(presum_i = 1\)的數量, 然後根據\(presum_j\)的取值,就知道對應的 \(i\)的數量。

得到\((i,j)\)對數\(cnt_k\)後,就得到對於當前的 \(k\),其對答案的貢獻就是 \(2^k cnt_k\)。對所有的 \(k\)累計求和即為答案。

注意上述的求法包括了 \(j = i\)的情況,而題意是 \(j > i\),所以最後再減去所有數的和即可。

神奇的程式碼
#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<int> a(n);
    for (auto& x : a)
        cin >> x;
    LL ans = 0;
    auto solve = [&](int bit) {
        vector<int> odd(n);
        for (int i = 0; i < n; ++i) {
            odd[i] = (a[i] >> bit) & 1;
        }
        for (int i = 1; i < n; ++i) {
            odd[i] = odd[i] ^ odd[i - 1];
        }
        map<int, int> cnt;
        cnt[0] = 1;
        LL sum = 0;
        for (int i = 0; i < n; ++i) {
            sum += cnt[odd[i] ^ 1];
            cnt[odd[i]]++;
        }
        return sum;
    };
    for (int i = 0; i < 32; ++i) {
        ans += (1ll << i) * solve(i);
    }
    ans -= accumulate(a.begin(), a.end(), 0ll);
    cout << ans << '\n';

    return 0;
}



F - Takahashi on Grid (abc365 F)

題目大意

二維網格,\(n \times m\),每一列有一個連續的空間是空地。每一列的空地必定相交。

回答\(q\)個問題。

每個問題,給定起點終點,每次可以上下左右走一格,問移動的最小步數。

解題思路

顯而易見的樸素做法的時間複雜度為\(O(nq)\),即對於每個詢問,從\(sx \to sx+1 \to sx+2 \to ... \to tx\),一列一列移動。

以下下標\((x,y)\)表示第\(x\)列第\(y\)行,注意與平時表示的意思不同。

考慮移動過程,其實就兩個階段:

  • 第一階段,能夠直接平行移動,即 \((x, y) \to (x + 1, y)\)
  • 第二階段,不能平行移動了,得上下移動,這時一定有\((x, y) \to (x, l[x+1])\)\((x, r[x+1])\)。即抵達下一列的上下兩個端點,然後往右邊繼續移動。

然後這兩階段不斷重複。

對於第一階段,可以透過預處理,在 \(O(\log n)\)的時間內找到平行移動到最右邊的邊界,即此時會有 \(y < l[x + 1]\)\(y > r[x + 1]\) ,因此用\(st\)表預處理 \(lmax[i,j]\)表示列 \([i,j]\)\(l\)的最大值, \(rmin[i,j]\)表示列 \([i,j]\)\(r\)的最小值 ,透過二分可以找到這個平行移動的邊界列。

然後是第二階段,注意到第二階段的開始起點只有\(2n\)個:每列要麼是從上端點開始,要麼從下端點開始。因此我們可以預處理出,從每個這樣的起點出發,到達每一列的代價之類的東西。

具體是怎樣的東西呢?注意到從這些端點出發,其實後續的路線就是固定了,所以可以用倍增的方法,預處理出從每個端點出發,走\(2^i\)步後,抵達到哪個列的上端點或下端點,代價是多少。

這裡的一步指的是必須變動\(y\)的,即經過一次第二階段,即,如果從\((x,y)\)能平行移動則一直平行移動,直到要上下移動才能抵達下一列\(s\),這樣就稱之為一步,從\(x \to s\)\(y \to l[s]\)\(y \to r[s]\)

定義了一步後,就可以預處理倍增陣列\(run[i][x][0/1]\)表示從 第 \(x\)行的上/下端點,走 \(i\)步後抵達的 (列,上/下端點,移動次數)這麼個三元組。

然後對於每個詢問,透過二分求出第一階段移動的移動次數,此時抵達到第二階段的一個起點,再透過倍增陣列得到移動到 \((tx, ty)\)的移動次數。這裡可能不能直接到\((tx, ty)\),可能最後再透過第一階段的平行移動到 \(tx\),再上下移動到 \(ty\),加個判斷即可。

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

// usage:
//   auto fun = [&](int i, int j) { return min(i, j); };
//   SparseTable<int, decltype(fun)> st(a, fun);
// or:
//   SparseTable<int> st(a, [&](int i, int j) { return min(i, j); });
template <typename T, class F = function<T(const T&, const T&)>>
class SparseTable {
  public:
    int n;
    vector<vector<T>> mat;
    F func;

    SparseTable(const vector<T>& a, const F& f) : func(f) {
        n = static_cast<int>(a.size());
        int max_log = 32 - __builtin_clz(n);
        mat.resize(max_log);
        mat[0] = a;
        for (int j = 1; j < max_log; j++) {
            mat[j].resize(n - (1 << j) + 1);
            for (int i = 0; i <= n - (1 << j); i++) {
                mat[j][i] = func(mat[j - 1][i], mat[j - 1][i + (1 << (j - 1))]);
            }
        }
    }

    T get(int from, int to) const { // [from, to]
        assert(0 <= from && from <= to && to <= n - 1);
        int lg = 32 - __builtin_clz(to - from + 1) - 1;
        return func(mat[lg][from], mat[lg][to - (1 << lg) + 1]);
    }
};

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<int> l(n), r(n);
    for (int i = 0; i < n; i++)
        cin >> l[i] >> r[i];
    vector<vector<vector<array<LL, 3>>>> run(
        31, vector<vector<array<LL, 3>>>(
                n, vector<array<LL, 3>>(2, array<LL, 3>{})));
    SparseTable<int> L(l, [&](int i, int j) { return max(i, j); });
    SparseTable<int> R(r, [&](int i, int j) { return min(i, j); });
    auto step = [&](int x, int y) {
        int l = x, r = n;
        while (l + 1 < r) {
            int m = (l + r) / 2;
            if (L.get(x, m) <= y && R.get(x, m) >= y) {
                l = m;
            } else {
                r = m;
            }
        }
        return l + 1;
    };
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < 2; ++j) {
            int x = i, y = (j ? r[i] : l[i]);
            int nxt = step(x, y);
            if (nxt == n) {
                run[0][i][j] = {n, 0, n - i};
            } else if (l[nxt] > y) {
                run[0][i][j] = {nxt, 0, l[nxt] - y + nxt - i};
            } else {
                run[0][i][j] = {nxt, 1, y - r[nxt] + nxt - i};
            }
        }
    }

    for (int i = 1; i <= 30; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int k = 0; k < 2; ++k) {
                auto [nxt, d, cost] = run[i - 1][j][k];
                if (nxt == n) {
                    run[i][j][k] = {n, 0, cost};
                } else {
                    auto [nnxt, dd, ccost] = run[i - 1][nxt][d];
                    run[i][j][k] = {nnxt, dd, cost + ccost};
                }
            }
        }
    }

    auto solve = [&](int sx, int sy, int tx, int ty) -> LL {
        if (sx == tx) {
            return abs(ty - sy);
        }
        if (sx > tx) {
            swap(sx, tx);
            swap(sy, ty);
        }
        int nxt = step(sx, sy);
        if (nxt > tx) {
            return abs(ty - sy) + tx - sx;
        }
        int sign = (sy < l[nxt] ? 0 : 1);
        LL ret = nxt - sx + (sign ? sy - r[nxt] : l[nxt] - sy);
        sx = nxt;
        for (int i = 30; i >= 0; --i) {
            auto [nnxt, dd, ccost] = run[i][sx][sign];
            if (nnxt <= tx) {
                ret += ccost;
                sx = nnxt;
                sign = dd;
            }
        }
        ret += tx - sx + (sign ? abs(ty - r[sx]) : abs(l[sx] - ty));
        return ret;
    };

    int q;
    cin >> q;
    while (q--) {
        int sx, sy, tx, ty;
        cin >> sx >> sy >> tx >> ty;
        --sx, --tx;
        LL ans = solve(sx, sy, tx, ty);
        cout << ans << '\n';
    }

    return 0;
}



G - AtCoder Office (abc365 G)

題目大意

給定\(n\)個員工在公司的若干條時間段,回答 \(q\)個問題。

每個問題,詢問兩個員工 \(i,j\)同時在公司的時間。

解題思路

<++>

神奇的程式碼



相關文章