Hướng dẫn cho SEQ198


Chỉ sử dụng khi thực sự cần thiết như một cách tôn trọng tác giả và người viết hướng dẫn này.

Chép code từ bài hướng dẫn để nộp bài là hành vi có thể dẫn đến khóa tài khoản.

Authors: SPyofgame

<style> .spoileralert-button { } .spoileralert-border { background: linear-gradient(33deg, #222222, #444444); background-clip: border-box; border-radius: 50px 15px 50px 15px; } .spoileralert-content{ padding: 15px; border: 3px dashed #666666; font-size: 15px; text-align:center; text-justify: inter-word; border-radius: 50px 15px 50px 15px; } .breakline-color { background: linear-gradient(to right, red, purple, blue, green); background-clip: border-box; } .breakline-float { margin-top: 25px; margin-bottom: 25px; width: 100%; height: 0%; color: none; border-top: 2px dashed white; } .Table-Of-Content { display: flex; } .Table-Of-Content Menu { padding: 0px 25px 0px 25px; } .Table-Of-Content header { width: 150px; height: 15px; color: black; background-color: #ddd; margin: 0px 25px 0px 25px; text-justify: center; padding-left: 20px; font-size: 16px; } .Table-Of-Content a { width: 170px; background-color: #eee; margin: 0px 25px 0px 25px; padding-left: 10px; display: block; text-justify: center; } .Table-Of-Content a:hover { text-decoration: underline; } .tooltip { position: relative; display: inline-block; border-bottom: 1px dotted black; } .tooltip .tooltiptext { visibility: hidden; width: 120px; background-color: rgba(0,0,0,85%); color: #fff; text-align: center; border-radius: 6px; padding: 5px 0; position: absolute; z-index: 1; bottom: 150%; left: 50%; margin-left: -60px; } .tooltip .tooltiptext::after { content: ""; position: absolute; top: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: black transparent transparent transparent; } .tooltip:hover .tooltiptext { visibility: visible; } .useless{} </style>

<button style="text-align:center; font-size:30px; color:none; background:none; border:none; padding:0; position:relative; left:38.5%;">
\(\color{#ff0000}{\text{Spoiler Alert}_{{}_{{}^{{}^{v2.9}}}}}\)
</button>

$\color{#ff7500}{\text{Khuyến khích bạn đọc trước khi đọc phần lời giải xin hãy thử code ra thuật của mình dù nó có sai hay đúng}}$ $\color{#ff7500}{\text{Và sau đó từ phần bài giải và thuật toán trước đó mà đối chiếu với thuật của mình và thu được bài học cho mình}}$ $\color{#ff7500}{\text{Mình xin rút kinh nghiệm và chấn chỉnh bản thân nếu trong editorial có gì sai sót, và bạn có thể gửi feedback }}$ [ở đây](https://www.facebook.com/SPectiar2k/)

<button style="text-align:center; font-size:30px; color:none; background:none; border:none; padding:0; position:relative; left:38.5%;">
\(\color{orange}{\text{Mục lục}}\)
</button>


\(\color{#300000}{\text{1. Hint <Trâu>}}\)

  • \(\color{#903030}{\text{<Cày trâu>}}\) Duyệt qua từng dãy con và kiểm tra xem dãy đó có thỏa hay không.

Gọi tập \(S\) là một tập các phần tử thỏa màn yêu cầu đề bài. Thì cần xóa \(n - |S|\) phần tử

Thì kết quả cần tìm là \(res = n - max(|S|)\)


\(\color{#c01515}{\text{1. Approach <Cày trâu> <Bitmasking>}}\)

  • \(\color{#f03030}{\text{<Cày trâu> <Bitmasking>}}\) Xét từng trạng thái hiện tại là dãy nhị phân \(mask\)

\(mask = x_0 \times 2^0 + x_1 \times 2^1 + \dots + x_{n - 1} \times 2^{n - 1}\) với \(xi = 1\) là tồn tại phần tử thứ \(i\) trong mảng \(a[]\)

Gọi mảng \(b[]\) là tập hợp các phần tử \(a[i]\)\(x_i = 1\)

Kiểm tra tính hợp lệ bằng cách duyệt \(\forall x, y \in b[]\)\(|x - y| \notin \{1, 8, 9\}\)

Trong các mảng \(b[]\) hợp lệ thì \(res = n - max(b.size)\)

  • \(\color{#f03030}{\text{<Phân tích độ phức tạp> }}\) Độ phức tạp \(O(2^n \times n^2)\)

Số lượng tập cần duyệt qua là \(O(2^n)\)

Duyệt lấy các phần tử cho mảng \(b[]\)\(O(n)\)

Chi phí kiểm tra là \(O(n^2)\)


\(\color{#88B4B4}{\text{1. Code tham khảo(TLE) }}\): Cày trâu, Bitmasking

\(^{^{\color{#7f5f3f}{\text{Complexity : }} O(2^n \times n^2)\ \color{#7f5f3f}{\text{time}}\ ||\ O(n)\ \color{#7f5f3f}{\text{memory}}}}\)

C++
int main()
{
    int n;
    cin >> n;

    vector<int> a(n);
    for (int &x : a) cin >> x;

    int res = n;
    int lim = 1 << n;
    for (int mask = 0; mask < lim; ++mask)
    {
        vector<int> b;
        for (int i = 0; i < n; ++i)
            if (mask & (1 << i))
                b.push_back(a[i]);

        bool SEQ189 = true;
        int m = b.size();
        for (int x : b)
            for (int y : b)
                for (int d : {1, 8, 9})
                    if (x - y == d)
                        SEQ189 = false;

        if (SEQ189) res = min(res, n - m);
    }

    cout << res;
    return 0;
}

\(\color{#300000}{\text{2. Hint <Quy hoạch động>}}\)

  • \(\color{#903030}{\text{Nhận xét (Sắp xếp): }}\) Thứ tự các phần tử được chọn không quan trọng, chỉ quan trọng \(|a_i - a_j| \in \{1, 8, 9\}\) hay không

Ta có thể sắp xếp mảng để tiện tính toán

  • \(\color{#903030}{\text{Nhận xét (Nén mảng): }}\) Nếu \(S\) là một tập thỏa mãn tối ưu và \(a_i \in S\) thì mọi \(a_j = a_i\) \((j \neq i)\) đều thuộc tập \(S\)

Nên ta chỉ cần các phần tử phân biệt, gọi \(b_i\) là giá trị này và \(c_i\) là số lần xuất hiện số đó

  • \(\color{#903030}{\text{Nhận xét (Bitmasking): }}\) Khi lấy lần lượt các phần tử \(x\) tăng dần. Mình chỉ quan tâm các thằng \(y \in S\)\(x - y \leq 9\) để kiểm tra \(S\) có thể thêm \(x\) hay không

Ngoài việc lưu một tập tối đa \(9\) phần tử, mình có thể dùng \(mask\) để đánh dấu sự tồn tại của nó như cách trên

  • \(\color{#903030}{\text{Nhận xét (Đệ quy có nhớ): }}\) Gọi \(magic(int\ i)\) là độ dài tập tối ưu xét tới \(i\)

Nếu ta bỏ qua thằng \(b_i\), kết quả tối ưu sẽ là \(magic(i + 1)\)

Nếu ta lấy thằng \(b_i\), kết quả tối ưu sẽ là \(magic(i + 1) + c_i\)


\(\color{#c01515}{\text{2. Approach <Quy hoạch động>}}\)

  • \(\color{#f03030}{\text{<Đệ quy có nhớ>}}\)

Gọi \(S[]\) là một mảng đánh dấu với \(S[p] = 1\) là tồn tại phần tử \(b_p\). Ta có thể tính được \(mask\) là 9 thằng cuối của \(S[]\)

Gọi \(f[i][mask]\) là mảng quy hoạch động. Nếu \(f[i][mask]\) đã được tính thì trả về kết quả luôn

Gọi \(ok = true\) là mình có thể lấy được phần tử \(b_i\) đang xét. Sau đó duyệt \(\forall \{t \in \mathbb{N}, t \in [i - 9; i)\ \}\) nếu \(\exists S[t] = 1\) để \(b_i - b_t \in \{1, 8, 9\}\) thì \(ok = false\)

Tính \(A = magic(i + 1)\) là khi không lấy \(b_i\).

Nếu \(ok = true\) thì ta sẽ lấy \(b_i\). Ta đánh dấu \(S[i] = true\) sau đó tính \(B = magic(i + 1)\) và cuối cùng đánh dấu lại \(S[i] = false\)

Kết quả \(f[i][mask] = max(A, B)\)

  • \(\color{#f03030}{\text{<Phân tích độ phức tạp> }}\) \(O(n \times 2^9)\)

Chi phí chuyển: \(O(9)\)

Số trạng thái: \(O(n) \times O(2^9)\)


\(\color{#009933}{\text{2. Code tham khảo(Accepted) }}\): Đệ quy có nhớ, Bitmasking

\(^{^{\color{#7f5f3f}{\text{Complexity : }} O(n \times 2^9)\ \color{#7f5f3f}{\text{time}}\ ||\ O(n \times 2^9)\ \color{#7f5f3f}{\text{memory}}}}\)

C++
#define all(x) (x).begin(), (x).end()
template<typename T> void maximize(T &res, T val) { if (res < val) res = val; }
template<typename T> void minimize(T &res, T val) { if (res > val) res = val; }

int n;
vector<int> b, c;
vector<vector<int> > f;
bitset<2000> S;
bool i189(int x) { return (x == 1) || (x == 8) || (x == 9); }
int magic(int i = 0) {
    if (i >= b.size()) return 0;

    int mask = 0;
    int low = max(0, i - 10);
    for (int t = low; t < i; ++t)
        mask |= (S[t] << (t - low));

    int &res = f[i][mask];
    if (res != -1) return res;
    res = magic(i + 1);

    bool ok = true;
    int lim = max(0, i - 9);
    for (int t = i - 1; t >= lim; --t)
        if (S[t] && i189(b[i] - b[t]))
            { ok = false; break; }

    if (ok)
    {
        S[i] = true;
        maximize(res, magic(i + 1) + c[i]);
        S[i] = false;
    }

    return res;
}

int main()
{
    cin >> n;
    vector<int> a(n);
    for (int &x : a)
        cin >> x;

    sort(all(a));
    int res = 0, cnt = 0, pre = -1;
    for (int cur : a)
    {
        if (cur != pre)
        {
            b.push_back(cur);
            c.push_back(1);
            cnt++;
        }
        else cnt++, c.back()++;
        pre = cur;
    }
    f.assign(n + 1, vector<int>((1 << 10) + 1, -1));
    res += cnt - magic();

    cout << res;
    return 0;
}


Bình luận

Không có bình luận nào.