AtCoder Beginner Contest 346

~Lanly~發表於2024-03-23

A - Adjacent Product (abc346 A)

題目大意

給定\(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;
    int la = 0;
    cin >> la;
    for (int i = 1; i < n; i++) {
        int a;
        cin >> a;
        cout << la * a << '\n';
        la = a;
    }

    return 0;
}



B - Piano (abc346 B)

題目大意

給定一個由wbwbwwbwbwbw無限拼接的字串。

給定\(w,b\),問是否由一個子串,其有 \(w\)w\(b\)b

解題思路

考慮列舉子串的起點,其實只有\(len(wbwbwwbwbwbw)=12\)種情況。

列舉起點後,統計其後的\(w+b\)個字元,看看是否滿足上述需求即可。

時間複雜度是 \(O(12(w+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);
    string s = "wbwbwwbwbwbw";
    int w, b;
    cin >> w >> b;
    int len = w + b;
    bool ok = false;
    for (int i = 0; i < s.size(); ++i) {
        map<char, int> cc;
        for (int j = i, cnt = 0; cnt < len; j = (j + 1) % s.size(), ++cnt) {
            cc[s[j]]++;
        }
        if (cc['w'] == w && cc['b'] == b) {
            ok = true;
            break;
        }
    }
    if (ok)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;

    return 0;
}



C - Σ (abc346 C)

題目大意

給定\(n\)個數 \(a_i\)

\(1 \sim k\)中,不是 \(a_i\)的數的和。

解題思路

先計算\(\sun_{i=1}{k}i = \frac{k(k+1)}{2}\),然後減去 \(a_i\)中出現過的數即可。注意要對 \(a_i\)去重,可以先 \(sort\)\(unique\),或者直接丟到 \(set\)裡。

神奇的程式碼
#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<int> a(n);
    for (auto& i : a)
        cin >> i;
    LL ans = 1ll * k * (k + 1) / 2;
    for (auto& i : set<int>(a.begin(), a.end())) {
        if (i <= k) {
            ans -= i;
        }
    }
    cout << ans << '\n';

    return 0;
}



D - Gomamayo Sequence (abc346 D)

題目大意

給定一個\(01\)字串\(s\),對第 \(i\)位翻轉需要 \(c_i\)的代價。

定義一個好的字串,當且僅當只有一個相鄰位置上的數字是相同的。

問將字串變成好的字串的最小代價。

解題思路

注意到好的字串的情況數只有\(O(n)\)個,其中 \(n\)是串 \(s\)的長度。因此我們可以列舉所有情況。

列舉相鄰數字相同的位置,然後計算變成 \(010101.....\)\(101010...\)這兩種情況的代價,所有位置情況代價取最小值即為答案。

如何快速計算代價?考慮到由相鄰數字相同的位置左右分割開來的都是固定的\(010101\)\(101010\),因此事先預處理所有字首和字尾變換成 \(010101\)\(101010\)的代價後,透過字首代價+字尾代價,就可以\(O(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;
    string s;
    cin >> n >> s;
    vector<int> c(n);
    for (auto& i : c)
        cin >> i;
    vector<array<LL, 2>> pre(n + 1), suff(n + 1);
    const string expect = "01";
    for (int i = 0; i < n; ++i) {
        pre[i][0] =
            (s[i] == expect[i & 1] ? c[i] : 0) + (i > 0 ? pre[i - 1][0] : 0);
        pre[i][1] =
            (s[i] == expect[~i & 1] ? c[i] : 0) + (i > 0 ? pre[i - 1][1] : 0);
    }
    for (int i = n - 1; i >= 0; --i) {
        suff[i][0] = (s[i] == expect[i & 1] ? c[i] : 0) +
                     (i < n - 1 ? suff[i + 1][0] : 0);
        suff[i][1] = (s[i] == expect[~i & 1] ? c[i] : 0) +
                     (i < n - 1 ? suff[i + 1][1] : 0);
    }
    LL ans = 1e18 + 7;
    ;
    for (int i = 0; i < n - 1; ++i) {
        ans =
            min({ans, pre[i][0] + suff[i + 1][1], pre[i][1] + suff[i + 1][0]});
    }
    cout << ans << '\n';

    return 0;
}



E - Paint (abc346 E)

題目大意

\(h \times w\)的平面,格子初始全為顏色 \(0\)

依次進行 \(m\)次操作,每次操作將某一行或某一列的格子塗成顏色 \(x_i\)

問最後各個顏色的格子數量。

解題思路

樸素的想法,最後統計每塊格子的顏色,時間複雜度避免不了為為\(O(hw)\)

考慮到每次操作都是對一行或一列塗色,其塗的格子數是已知的,所以可以直接累計結果。

但這樣的問題是,後面的操作會影響到前面的結果,顏色會覆蓋,導致先前塗的顏色數量可能會減少。

注意到後面的操作會覆蓋前面的操作,如果我們對操作反過來考慮,先考慮最後一次操作,再考慮前一個操作,那麼後考慮的操作不會影響先考慮的操作,就不會出現上面的先前塗的顏色會減少的問題。

因此我們對操作倒過來考慮,每次操作所塗的格子數。格子數的求法比較簡單,比如一次操作對某一行塗色,其塗得格子數為\(w-\)已經塗過的列運算元。維護一下已經塗過的列和行數量即可。當然還要維護某一列和某一行是否被塗過,後塗的操作是無效的。

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

const int up = 2e5 + 8;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int h, w, m;
    cin >> h >> w >> m;
    vector<array<int, 3>> op(m);
    for (auto& [t, a, x] : op) {
        cin >> t >> a >> x;
        --a;
    }
    vector<LL> cnt(up, 0);
    vector<int> used_row(h, 0);
    vector<int> used_col(w, 0);
    int row = h, col = w;
    reverse(op.begin(), op.end());
    for (auto& [t, a, x] : op) {
        if (t == 1) {
            if (used_row[a])
                continue;
            used_row[a] = 1;
            --row;
            cnt[x] += col;
        } else {
            if (used_col[a])
                continue;
            used_col[a] = 1;
            --col;
            cnt[x] += row;
        }
    }
    cnt[0] += 1ll * h * w - accumulate(cnt.begin(), cnt.end(), 0ll);
    vector<pair<int, LL>> ans;
    for (int i = 0; i < up; ++i) {
        if (cnt[i] != 0)
            ans.emplace_back(i, cnt[i]);
    }
    cout << ans.size() << '\n';
    for (auto& [i, c] : ans) {
        cout << i << " " << c << '\n';
    }

    return 0;
}



F - SSttrriinngg in StringString (abc346 F)

題目大意

給定兩個字串\(s,t\),定義 \(f(s,n)\)表示將字串 \(s\)重複拼接 \(n\)次。 \(g(t,k)\)表示將 \(t\)的每個字元重複 \(k\)次得到。

給定 \(n\),問最大的 \(k\),使得 \(g(t,k)\)\(f(s,n)\)的子序列。

解題思路

考慮\(k\)怎麼確定,如果我列舉 \(k\),那剩下的問題就是判斷子序列,容易發現這個可以在 \(O(|t|)\)內判斷。但 \(k\)最高可高達 \(10^{17}\),列舉不現實。

注意到\(k\)越小越好滿足, \(k\)越大越難滿足是子序列,容易發現\(k\)是否是子序列具有單調性,因此可以二分\(k\)

二分\(k\)後,就按照匹配子序列那樣(就近匹配)的暴力匹配 \(t\)的每個字元即可。只是細節有點多。

匹配 \(t\)的每個字元時,每個字元\(c\)\(k\)個,假設 \(s\)有該字元 \(cnt_c\)個,那先每 \(cnt_c\)\(cnt_c\)個匹配,耗掉 \(\frac{k}{cnt_c}\)\(s\),然後剩下的 \(k \% cnt_c\)\(c\)匹配 新一份的\(s\)的一部分。此時剩下的 字元就匹配\(t\)的下一個字元。

因此二分驗證時需要維護資訊有:

  • 當前匹配的是 \(t\)的第\(cur\)個字元
  • 當前字元還剩下 \(left\)個要匹配,
  • 當前用了 \(sum\)\(s\)
  • 當前份正匹配到第\(it\)個字元。
神奇的程式碼
#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);
    LL n;
    string s, t;
    cin >> n >> s >> t;
    vector<vector<int>> pos(26);
    for (int i = 0; i < s.size(); ++i) {
        pos[s[i] - 'a'].push_back(i);
    }
    LL l = 0, r = 1e18;
    auto check = [&](LL x) {
        if (x == 0)
            return true;
        LL sum = 0;
        int cur = 0;
        int it = 0;
        LL left = x;
        while (sum <= n && cur < t.size()) {
            int c = t[cur] - 'a';
            if (pos[c].size() == 0)
                return false;
            if (it == 0) {
                int cnt = pos[c].size();
                LL cost = left / cnt;
                sum += cost;
                left -= cost * cnt;
                LL mod = left % cnt;
                if (mod != 0) {
                    sum += 1;
                    it = (pos[c][mod - 1] + 1) % s.size();
                    left -= mod;
                } else {
                    it = (pos[c].back() + 1) % s.size();
                }
            } else {
                int st = lower_bound(pos[c].begin(), pos[c].end(), it) -
                         pos[c].begin();
                int cnt = pos[c].size() - st;
                if (left > cnt) {
                    left -= cnt;
                    it = 0;
                } else {
                    it = (pos[c][st + left - 1] + 1) % s.size();
                    left = 0;
                }
            }
            if (left == 0) {
                ++cur;
                left = x;
            }
        }
        return sum <= n;
    };
    while (l + 1 < r) {
        LL mid = (l + r) >> 1;
        if (check(mid))
            l = mid;
        else
            r = mid;
    }
    cout << l << '\n';

    return 0;
}



G - Alone (abc346 G)

題目大意

給定\(n\)個數\(a_i\)。問 \((l,r)\)的數量,滿足\(a[l..r]\)中有數字僅出現一次。

解題思路

幾個常規思路,比如列舉\(r\), 看有幾個符合條件的\(l\),或者分治之類的,都不行,棘手在於有數字僅出現一次這一條件不好處理。

那就嘗試列舉 僅出現一次的數字,記該數字\(x\)的左右兩邊最近的數字\(x\) 的位置\(l_x, r_x\),那麼所有滿足 \(l \in [l_x + 1, i], r \in [i, r_x - 1]\) 條件的區間\((l,r)\)都是滿足題意的區間,個數即為兩個可行區間大小的乘積。

所有的這樣的區間個數加起來,但不是答案,會有算重的。比如一個區間 \((l,r)\)包含了兩個僅出現一次的數字,那麼這個區間會算成兩份。關鍵是如何去重。

如何理解去重,設想一個二維平面,橫座標是\(l\),縱座標是 \(r\),那麼上述的每一個 \(l \in [l_x + 1, i], r \in [i, r_x - 1]\) 就對應一個矩形,矩形之間可能有交。答案就是矩形的面積。

從這個角度理解的話,解法就呼之欲出了:掃描線掃一遍就是答案了。

程式碼實現裡,因為掃描線是列舉一維(比如\(r\)),用線段樹維護另一維(\(l\) 的可行位置個數),而這裡\(r\)最大隻有 \(n\),因此可以直接列舉,算一層一層的結果,而不必像平時掃描線裡的離散化之類的。列舉每個 \(r\)之後,更新 \(l\)的可行區域。

到最後其實就是列舉 \(r\),看有幾個符合條件的\(l\)這樣的思路,只是中間怎麼維護比較難思考,理解矩形面積+掃描線後就能更好想到做法了。

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

const int N = 2e5 + 8;

class segment {
#define lson (root << 1)
#define rson (root << 1 | 1)
  public:
    LL minn[N << 2];
    LL cnt[N << 2];
    LL lazy[N << 2];

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

    void pushdown(int root) {
        if (lazy[root]) {
            minn[lson] += lazy[root];
            minn[rson] += lazy[root];
            lazy[lson] += lazy[root];
            lazy[rson] += lazy[root];
            lazy[root] = 0;
        }
    }

    void update(int root, int l, int r, int L, int R, LL val) {
        if (L <= l && r <= R) {
            minn[root] += val;
            lazy[root] += val;
            return;
        }
        pushdown(root);
        int mid = (l + r) >> 1;
        if (L <= mid)
            update(lson, l, mid, L, R, val);
        if (R > mid)
            update(rson, mid + 1, r, L, R, val);
        minn[root] = min(minn[lson], minn[rson]);
        cnt[root] = (minn[lson] == minn[root] ? cnt[lson] : 0) +
                    (minn[rson] == minn[root] ? cnt[rson] : 0);
    }

    pair<int, int> query(int root, int l, int r, int L, int R) {
        if (L <= l && r <= R) {
            return {minn[root], cnt[root]};
        }
        pushdown(root);
        int mid = (l + r) >> 1;
        pair<int, int> resl = {INT_MAX, 0}, resr = {INT_MAX, 0};
        if (L <= mid)
            resl = query(lson, l, mid, L, R);
        if (R > mid)
            resr = query(rson, mid + 1, r, L, R);
        pair<int, int> res;
        res.first = min(resl.first, resr.first);
        res.second = (resl.first == res.first ? resl.second : 0) +
                     (resr.first == res.first ? resr.second : 0);
        return res;
    }
} 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;
        --x;
    }
    sg.build(1, 1, n);
    vector<vector<int>> pos(n, vector<int>(1, 0));
    LL ans = 0;
    for (int i = 0; i < n; i++) {
        auto& history = pos[a[i]];
        int la = history.back();
        if (history.size() > 1) {
            int lla = history[history.size() - 2];
            sg.update(1, 1, n, lla + 1, la, -1);
        }
        int cur = i + 1;
        sg.update(1, 1, n, la + 1, cur, 1);
        auto [minn, cnt] = sg.query(1, 1, n, 1, cur);
        ans += cur - (minn == 0 ? cnt : 0);
        history.push_back(cur);
    }
    cout << ans << '\n';

    return 0;
}


相關文章