Hướng dẫn giải của [Beginner Free Contest 11] CONSEQ


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.

Hướng dẫn giải

Đếm đoạn con mà các số trong đó là một dãy số nguyên liên tiếp, mỗi số xuất hiện đúng một lần (tức khác nhau và max - min = độ dài - 1).

Sub 1: Vét cạn, ~N \le 5000~ ~O(N^2)~

Với mỗi đầu l, mở rộng r và kiểm tra điều kiện bằng max, min và đếm trùng. Chạy riêng subtask này đạt 12/20 test (các test có N lớn nằm ngoài miền).

Sub 2: Cây đoạn, ~N \le 10^5~ ~O(N \log N)~

Với mỗi r, các l hợp lệ nằm trong đoạn [lo..r] (do trùng lặp). Trong đoạn đó h(l) = max - min + l luôn >= r; cần đếm số l có h(l) = r bằng cây đoạn có cập nhật cộng đoạn theo ngăn xếp max/min. Chạy riêng subtask này đạt 20/20 test.

Chương trình hoàn chỉnh

Nếu N <= 5000 dùng sub 1, ngược lại dùng sub 2.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int n;
vector<ll> a;
vector<int> id;   // chi so nen cua a[i] (de dem so lan xuat hien)

// Dem doan [l..r] co do dai <= maxLen thoa man: cac so doi mot khac nhau va max - min + 1 = do dai
// (khi do k, k+1, ..., k+len-1 xuat hien dung mot lan)
ll countWith(int maxLen) {
    vector<int> cnt(n, 0);
    ll ans = 0;
    for (int l = 0; l < n; l++) {
        ll mn = LLONG_MAX, mx = LLONG_MIN;
        int r = l;
        for (; r < n && r - l < maxLen; r++) {
            if (cnt[id[r]]++) break;   // trung lap: cac doan dai hon cung trung
            mn = min(mn, a[r]);
            mx = max(mx, a[r]);
            if (mx - mn + 1 == r - l + 1) ans++;
        }
        for (int i = l; i <= min(r, n - 1); i++) cnt[id[i]] = 0;
    }
    return ans;
}

// Sub 1: vet can O(N^2), dung cho N <= 5000 voi gia tri bat ky
void sub1() {
    cout << countWith(n) << "\n";
}

// Cay doan: voi moi l, luu h(l) = max(l..r) - min(l..r) + l, va dem so vi tri dat gia tri nho nhat
struct Seg {
    int n;
    vector<ll> mn, lz;
    vector<int> cnt;
    void init(int nn) {
        n = nn;
        mn.assign(4 * n, 0);
        lz.assign(4 * n, 0);
        cnt.assign(4 * n, 0);
        build(1, 0, n - 1);
    }
    void build(int x, int l, int r) {
        if (l == r) { mn[x] = l; cnt[x] = 1; return; }
        int m = (l + r) / 2;
        build(2 * x, l, m);
        build(2 * x + 1, m + 1, r);
        pull(x);
    }
    void pull(int x) {
        if (mn[2 * x] < mn[2 * x + 1]) { mn[x] = mn[2 * x]; cnt[x] = cnt[2 * x]; }
        else if (mn[2 * x] > mn[2 * x + 1]) { mn[x] = mn[2 * x + 1]; cnt[x] = cnt[2 * x + 1]; }
        else { mn[x] = mn[2 * x]; cnt[x] = cnt[2 * x] + cnt[2 * x + 1]; }
    }
    void apply(int x, ll v) { mn[x] += v; lz[x] += v; }
    void push(int x) {
        if (lz[x]) { apply(2 * x, lz[x]); apply(2 * x + 1, lz[x]); lz[x] = 0; }
    }
    void add(int x, int l, int r, int ql, int qr, ll v) {
        if (qr < l || r < ql) return;
        if (ql <= l && r <= qr) { apply(x, v); return; }
        push(x);
        int m = (l + r) / 2;
        add(2 * x, l, m, ql, qr, v);
        add(2 * x + 1, m + 1, r, ql, qr, v);
        pull(x);
    }
    pair<ll, int> query(int x, int l, int r, int ql, int qr) {
        if (qr < l || r < ql) return {LLONG_MAX, 0};
        if (ql <= l && r <= qr) return {mn[x], cnt[x]};
        push(x);
        int m = (l + r) / 2;
        auto L = query(2 * x, l, m, ql, qr);
        auto R = query(2 * x + 1, m + 1, r, ql, qr);
        if (L.first < R.first) return L;
        if (R.first < L.first) return R;
        return {L.first, L.second + R.second};
    }
};

// Sub 2: tong quat O(N log N): cua so [lo..r] la cac vi tri co cac so doi mot khac nhau;
// doan [l..r] thoa dieu kien khi max - min = r - l, tuc h(l) = r voi h(l) = max - min + l (luon >= r)
void sub2() {
    Seg seg;
    seg.init(n);
    vector<int> smax, smin;        // ngan xep chi so cho max va min
    unordered_map<ll, int> last;   // vi tri xuat hien gan nhat cua moi gia tri
    int lo = 0;
    ll ans = 0;
    for (int r = 0; r < n; r++) {
        while (!smax.empty() && a[smax.back()] <= a[r]) {
            int t = smax.back();
            smax.pop_back();
            int lft = smax.empty() ? 0 : smax.back() + 1;
            seg.add(1, 0, n - 1, lft, t, a[r] - a[t]);   // max tang tu a[t] len a[r]
        }
        smax.push_back(r);
        while (!smin.empty() && a[smin.back()] >= a[r]) {
            int t = smin.back();
            smin.pop_back();
            int lft = smin.empty() ? 0 : smin.back() + 1;
            seg.add(1, 0, n - 1, lft, t, a[t] - a[r]);   // min giam tu a[t] xuong a[r]
        }
        smin.push_back(r);
        auto it = last.find(a[r]);
        if (it != last.end()) lo = max(lo, it->second + 1);
        last[a[r]] = r;
        auto q = seg.query(1, 0, n - 1, lo, r);
        if (q.first == r) ans += q.second;
    }
    cout << ans << "\n";
}

int main() {
    cin >> n;
    a.resize(n);
    for (auto& x : a) cin >> x;
    if (n <= 5000) {
        vector<ll> v = a;
        sort(v.begin(), v.end());
        v.erase(unique(v.begin(), v.end()), v.end());
        id.resize(n);
        for (int i = 0; i < n; i++) id[i] = lower_bound(v.begin(), v.end(), a[i]) - v.begin();
        sub1();
    } else {
        sub2();
    }
    return 0;
}

Bình luận

Hãy đọc nội quy trước khi bình luận.


Không có bình luận tại thời điểm này.