AtCoder Beginner Contest 381

~Lanly~發表於2024-11-25
省流版
  • A. 按題意判斷即可
  • B. 按題意判斷即可
  • C. 列舉/的位置,然後分別向左右找到最長的1串和2串,然後取最小值即可
  • D. 討論起始位置的奇偶性,然後用雙指標,每兩個字元每兩個字元,維護出現的次數為2,兩種情況取最大值即可
  • E. 答案為所有/的左右12個數的最小值的最大值,注意到個數隨著/的增大是單調的,而最大值出現在交點,二分找該交點即可
  • F. \(dp[i]\)表示已經選擇的字元的集合狀態是\(i\)時的最小的選擇字元的位置,轉移列舉下一個字元,然後找到下一個字元的位置,然後更新\(dp\)即可

A - 11/22 String (abc381 A)

題目大意

給定一個字串,問它是不是形如11/22的形式。即一個1串和一個2串,長度相等,中間用/分隔。

解題思路

找到/,然後判斷左右兩邊是否是1串和2串即可。

神奇的程式碼
n = input()
s = input()
s = s.split('/')
if len(s) == 2 and len(s[0]) == len(s[1]) and all(i == '1' for i in s[0]) and all(i == '2' for i in s[1]):
    print('Yes')
else:
    print('No')


B - 1122 String (abc381 B)

題目大意

給定一個字串,問它是不是形如11223344的形式。即每兩位相同,且每個字元出現的次數為2。

解題思路

判斷奇數位和偶數位是否相同,然後判斷每個字元出現的次數是否為2即可。

神奇的程式碼
s = input()
c = set(s)
if s[::2] == s[1::2] and all(sum(1 for i in s if i == j) == 2 for j in c):
    print('Yes')
else:
    print('No')


C - 11/22 Substring (abc381 C)

題目大意

給定一個字串,問它的最長子串是多少,使得子串中1的個數和2的個數相等,中間用/分隔。

解題思路

列舉/的位置,然後分別向左右找到最長的1串和2串,然後取最小值即可。

因為每個子串只包含一個/,因此字串就被/分成若干部分,每個部分最多隻會遍歷兩次,因此時間複雜度為\(O(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;
    string s;
    cin >> n >> s;
    int ans = 0;
    auto find1 = [&](int x) {
        for (int i = x; i >= 0; --i) {
            if (s[i] != '1')
                return x - i;
        }
        return x + 1;
    };
    auto find2 = [&](int x) {
        for (int i = x; i < n; ++i) {
            if (s[i] != '2')
                return i - x;
        }
        return n - x;
    };
    for (int i = 0; i < n; i++) {
        if (s[i] == '/') {
            ans = max(ans, min(find1(i - 1), find2(i + 1)));
        }
    }
    ans = ans * 2 + 1;
    cout << ans << '\n';

    return 0;
}



D - 1122 Substring (abc381 D)

題目大意

給定一個字串,問它的最長子串是多少,使得每兩位相同,且每個字元出現的次數為2。

解題思路

如果每兩個看成一個字元,問題其實就是求最長的子串,其每個字元出現的次數為1。這是一個經典的雙指標問題,即維護一個區間\(l,r\),使得區間內的每個字元出現的次數為1,然後不斷向右移動\(r\),直到區間內的字元出現的次數不滿足條件,然後向右移動\(l\),直到區間內的字元出現的次數滿足條件,然後繼續向右移動\(r\),直到字串結束。因為每個字元最多隻會被訪問兩次,因此時間複雜度為\(O(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& i : a) {
        cin >> i;
        --i;
    }
    auto solve = [&](int st) {
        int ret = 0;
        int la = st;
        vector<int> cnt(n);
        for (int i = st; i < n; i += 2) {
            if (i + 1 >= n || a[i] != a[i + 1] || cnt[a[i]] == 1) {
                ret = max(ret, i - la);
                while (la < i && (a[i] != a[i + 1] || cnt[a[i]] == 1)) {
                    cnt[a[la]]--;
                    la += 2;
                }
            }
            if (a[i] == a[i + 1])
                cnt[a[i]]++;
            else
                la += 2;
        };
        ret = max(ret, n - la - ((n - st) & 1));
        return ret;
    };
    int ans = max(solve(0), solve(1));
    cout << ans << '\n';

    return 0;
}



E - 11/22 Subsequence (abc381 E)

題目大意

給定一個字串\(s\),給定\(q\)個詢問,每個詢問給定\(l,r\),問\(s[l,r]\)的最長子序列,使得子序列中1的個數和2的個數相等,中間用/分隔。

解題思路

對於每個詢問,列舉/的位置\(m\),答案就是\([l,m]\)\(1\)的個數和\([m+1,r]\)\(2\)的個數的最小值的兩倍加一。後者個數的計算可以透過字首和來實現。但前者列舉的複雜度是\(O(n)\),因此總的複雜度是\(O(nq)\),無法透過。

注意答案是\(\min(cnt1[l, m], cnt2[m, r])\),其中\(cnt1[l, m]\)表示\([l,m]\)\(1\)的個數,\(cnt2[m, r]\)表示\([m+1,r]\)\(2\)的個數。隨著\(m\)的增大,\(cnt1[l, m]\)是單調遞增的,\(cnt2[m, r]\)是單調遞減的。

因此兩者最小值,首先是取在\(cnt1\),然後再變成在\(cnt2\),而其最大值就在這兩者的交點。因此可以透過二分來找到這個交點。這樣就可以將複雜度降到\(O(q\log n)\)

雖然這個\(\min\)是個單峰函式,但由於有相同的值,不能直接三分找到最大值。

神奇的程式碼
#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;
    string s;
    cin >> n >> q >> s;
    array<vector<int>, 2> cnt{vector<int>(n), vector<int>(n)};
    vector<int> pos;
    for (int i = 0; i < n; i++) {
        if (s[i] == '/')
            pos.push_back(i);
        else
            cnt[s[i] - '1'][i] = 1;
    }
    partial_sum(cnt[0].begin(), cnt[0].end(), cnt[0].begin());
    partial_sum(cnt[1].begin(), cnt[1].end(), cnt[1].begin());
    auto get_sum = [&](int l, int r, int c) {
        return cnt[c][r] - (l - 1 < 0 ? 0 : cnt[c][l - 1]);
    };
    auto solve = [&](int L, int R) {
        auto calc1 = [&](int x) { return get_sum(L, x, 0); };
        auto calc2 = [&](int x) { return get_sum(x, R, 1); };
        auto check = [&](int x) { return calc1(x) <= calc2(x); };

        int l = lower_bound(pos.begin(), pos.end(), L) - pos.begin();
        int r = upper_bound(pos.begin(), pos.end(), R) - pos.begin();
        if (l == r)
            return 0;
        int ret = 0;
        while (l + 1 < r) {
            int m = (l + r) / 2;
            if (check(pos[m]))
                l = m;
            else
                r = m;
        }
        for (int i = l; i <= r; i++) {
            if (L <= pos[i] && pos[i] <= R)
                ret = max(ret, min(calc1(pos[i]), calc2(pos[i])));
        }
        return ret * 2 + 1;
    };
    while (q--) {
        int l, r;
        cin >> l >> r;
        --l, --r;
        int ans = solve(l, r);
        cout << ans << '\n';
    }

    return 0;
}



F - 1122 Subsequence (abc381 F)

題目大意

給定一個字串,問它的最長子序列是多少,使得每兩位相同,且每個字元出現的次數為2。

解題思路

注意字元只有\(20\)種。一種樸素搜尋就是花\(O(20!)\)列舉字元出現的順序,一旦順序確定好了,就是可行性判斷,找出這個子序列就可以用貪心的方法,即每次找到一個字元,然後找到下一個字元,直到找到所有的字元。這樣的複雜度是\(O(n)\)的。

考慮如何最佳化呢,找到其中重複的部分,比如考慮排列123 4567以及213 4567以及312 4567,我們要依次判斷這些排列的可行性,比如我們找到4,就要從上一次的位置開始找,對於123213312,這所謂的上一次的位置可能是不一樣的,但因為我們要找4,自然希望上一次位置越靠前越好。而對於我們找4還是找5,取決於我們之前是否選擇了4。因此這裡可以把排列的資訊壓縮掉,即記\(dp[i]\)表示已經選擇的字元的集合狀態是\(i\)時的最小的選擇字元的位置。比如狀態\(i\)表示選擇了123,那\(dp[i]\)就是選擇順序123, 132, 213, 231, 312, 321按照上述貪心方法找到的子序列最大下標的最小值。

轉移就是列舉下一個字元,然後找到下一個字元的位置,然後更新\(dp\)即可。時間複雜度是\(O(2^{20} 20)\)的。

神奇的程式碼
#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;
    constexpr int num = 20;
    array<vector<int>, num> pos;
    for (int i = 0; i < n; i++) {
        int a;
        cin >> a;
        --a;
        pos[a].push_back(i);
    }
    constexpr int up = (1 << num);
    vector<int> dp(up, n);
    dp[0] = 0;
    int ans = 0;
    for (int i = 0; i < up; ++i) {
        for (int j = 0; j < num; ++j) {
            if ((i >> j) & 1) {
                int la = i ^ (1 << j);
                auto it = lower_bound(pos[j].begin(), pos[j].end(), dp[la]);
                if (it != pos[j].end() && next(it) != pos[j].end()) {
                    dp[i] = min(dp[i], *next(it));
                    ans = max(ans, __builtin_popcount(i));
                }
            }
        }
    }
    cout << ans * 2 << '\n';

    return 0;
}



G - Fibonacci Product (abc381 G)

題目大意

定義\(a_1 = x, a_2 = y, a_i = a_{i-1} + a_{i - 2}\)

\(\prod_{i=1}^{n} (a_i) \mod 998244353\)

解題思路

<++>

神奇的程式碼