Hướng dẫn giải của Bedao OI Contest 3 - Sort and Mex query


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.

Ở subtask thứ ~2~, việc tính ra giá trị ~Mex~ trong đoạn ~[l; r]~ trên mảng ~a~ ta có thể dễ dàng thực hiện nếu với mỗi giá trị ~x~ (~0 \le x \le 30~) ta tính được mảng ~cnt_x~ là số lượng số ~x~ trong đoạn ~[l; r]~ trên mảng ~a~.

Có được mảng ~cnt~ này ta cứ thử lần lượt các giá trị x tăng dần từ ~0~ đến ~30~, nếu giá trị nào chưa xuất hiện trong mảng (~cnt_x = 0~) ta biết được ngay giá trị ~Mex~ cần tìm là ~x~. Còn không, mọi giá trị từ ~0~ đến ~30~ đều xuất hiện thì giá trị ~Mex~ sẽ là 31.

Từ đây để tính nhanh mảng ~cnt~, cùng với việc không có truy vấn update trong subtask này, ta có thể sử dụng prefix sum cho từng giá trị ~x~.

Độ phức tạp ~O(q \cdot 31)~.

Ở subtask ~3~, nhìn vào truy vấn sắp xếp, ta thấy rằng, vì giá trị của ~a_i~ khá nhỏ (~0 \le a_i \le 30~) cho nên ta có thể sử dụng ý tưởng counting sort.

Xét truy vấn sort tăng dần, sau khi sắp xếp đoạn ~[l; r]~ sẽ có dạng kiểu ~cnt_0~ số ~0~ ở đầu, sau đó là ~cnt_1~ số ~1~ tiếp theo, ~cnt_2~ số ~2~ liền sau đó, ~\cdots~, ~cnt_{30}~ số ~30~ ở cuối.

Để thực hiện nhanh được việc lấy ra số lượng ở ~1~ đoạn và gán nhanh ~1~ đoạn liên tiếp ~1~ giá trị nào đó, ta có thể sử dụng segment tree với lazy update. Ta sẽ có ~31~ cây segment tree để quản lý số lượng vị trí trên mảng ~a~ mà có giá trị x trong đoạn.

Bây giờ ở truy vấn sort, lúc đầu ta có thể tính ra được nhanh mảng ~cnt_x~ trong đoạn mà ta cần sort, sau đó là thực hiện counting sort như trên. Ta gán ~cnt_0~ vị trí đầu tiên từ ~l~ là số ~0~ tương đương với việc, đầu tiên là xoá hết mọi vị trí mà có giá trị là ~0~ cũ trong đoạn bằng cách gán mọi phần tử trong đoạn ~[l; r]~ trên cây segment tree ~0~ bằng ~0~, sau đó gán trên cây segment tree ~0~ từ ~l~ đến ~l + cnt_0 - 1~ bằng ~1~. Ta cứ tiếp tục làm như vậy với từng giá trị tiếp theo, xoá hết mọi vị trí mà có giá trị là ~1~ cũ trong đoạn bằng cách gán mọi phần tử từ ~l~ đến ~r~ bằng ~0~ trên cây segment tree ~1~, rồi sau đó gán từ ~l + cnt_0~ đến ~l + cnt_0 + cnt_1 - 1~ trên cây segment tree ~1~ bằng ~1~. Ta tiếp tục vậy với mọi giá trị đến 30 là xong. Còn ở việc giảm dần cũng tương tự như vậy.

Ở truy vấn tìm ~Mex~ ta có thể thực hiện giống như subtask ~2~ với mảng ~cnt_x~ ta có thể tính nhanh bằng cách sử dụng ~31~ cây segment tree mà ta đã có sẵn.

Độ phức tạp ~O(q \cdot log2(n) \cdot 31)~.

#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 3e5 + 1;
const int MAX_VAL = 31 + 1;

int n;
int a[MAX_N];
int lastAns;
int ST[MAX_VAL][MAX_N << 2];
int lazy[MAX_VAL][MAX_N << 2];
int currCnt[MAX_VAL];

void down(int i, int id, int l, int r) {
    if (!lazy[i][id]) return;
    int k = lazy[i][id];

    if (k == 1) {
        ST[i][id << 1] = ST[i][id << 1 | 1] = 0;
    }
    else {
        int mid = (l + r) >> 1;
        ST[i][id << 1] = mid - l + 1;
        ST[i][id << 1 | 1] = r - mid;
    }

    lazy[i][id << 1] = k;
    lazy[i][id << 1 | 1] = k;

    lazy[i][id] = 0;
}

void update(int i, int id, int l, int r, int u, int v, int val) {
    if (u > r || v < l) return;

    if (u <= l && r <= v) {
        if (val) {
            ST[i][id] = r - l + 1;
        }
        else {
            ST[i][id] = 0;
        }
        lazy[i][id] = val + 1;
        return;
    }

    int mid = (l + r) >> 1;
    down(i, id, l, r);
    update(i, id << 1, l, mid, u, v, val);
    update(i, id << 1 | 1, mid + 1, r, u, v, val);

    ST[i][id] = ST[i][id << 1] + ST[i][id << 1 | 1];
}

int get(int i, int id, int l, int r, int u, int v) {
    if (u > r || v < l) return 0;

    if (u <= l && r <= v) {
        return ST[i][id];
    }

    int mid = (l + r) >> 1;
    down(i, id, l, r);
    return get(i, id << 1, l, mid, u, v) + get(i, id << 1 | 1, mid + 1, r, u, v);
}

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        update(a[i], 1, 1, n, i, i, 1);
    }

    int numCases = 0;
    cin >> numCases;
    for (int testCase = 1; testCase <= numCases; testCase++) {
        char type;
        cin >> type;

        int x, y;
        cin >> x >> y;

        if (x > y) swap(x, y);

        if (type == '1') {
            int type;
            cin >> type;

            for (int val = 0; val <= 30; val++) {
                currCnt[val] = get(val, 1, 1, n, x, y);
                update(val, 1, 1, n, x, y, 0);
            }

            int i = x;
            int step = 1, currVal = 0;
            if (type == 2) step = -1, currVal = 30;

            while (i <= y) {
                if (currCnt[currVal]) {
                    update(currVal, 1, 1, n, i, i + currCnt[currVal] - 1, 1);
                }
                i += currCnt[currVal];
                currVal += step;
            }
        }
        else {
            for (int val = 0; val <= 31; val++) {
                if (!get(val, 1, 1, n, x, y)) {
                    cout << val << '\n';
                    lastAns = val;
                    break;
                }
            }
        }
    }

    return 0;
}

/*


*/

Bình luận

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



  • 12
    LeThanhMinh  đã bình luận lúc 29, Tháng 11, 2023, 4:30 chỉnh sửa

    Solution bài này khá hay :>.

    Mình xin comment thêm một cách làm là :

    • Thay vì bạn dựng 30 segment tree thì bạn có thể áp dụng một hướng nhìn như sau . Vì giới hạn của bài là từ 0 đến 30 nên bạn sẽ for trâu thử hết 31 kết quả vào các truy vấn tìm MEX. Với mỗi số X từ [0;30] bạn sẽ ánh xạ mảng A như sao A[i] = 0 nếu A[i] < X , A[i] = 2 nếu A[i] = X và A[i] = 1 nếu A[i] > X.
    • Nhận thấy các thao tác sort tăng dần tương ứng với việc dồn toàn bộ số 0 về đầu , số 2 về giữa và số 1 về cuối vì ánh xạ.
    • Ngược lại với việc sort giảm dần số 1 về đầu tiếp đến là các số 2 và cuối cùng là số 0.
    • Sau đó với các truy vấn 3 bạn chỉ cần kiểm tra có số 2 nào nằm trong đoạn [l , r] hay không , nếu không thì số X là kết quả của truy vấn đó.
    • Ta sử dụng lazy update , nhưng lưu ý cần giảm thiểu số lượng update , get nhất có thể nếu không muốn bị TLE.

    Code tham khảo mình để ở đây


    • -2
      hoabanmatuyaccclone  đã bình luận lúc 29, Tháng 11, 2023, 13:17

      á đùuu cách này hay dữ