Editorial for SGAME6


Remember to use this editorial only when stuck, and not to copy-paste code from it. Please be respectful to the problem author and editorialist.

Submitting an official solution before solving the problem yourself is a bannable offence.
<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 <Bài toán con tương tự>}}\)

  • \(\color{#903030}{\text{<Xét bài toán con> }}\) Sau khi mình lấy phần tử đầu. Thì mình lần lượt lấy 2 phần tử kề với phần tử trước đó. Vậy nếu duỗi cái hình tròn đó ra thành một đoạn thẳng, ta được một hàng đợi hai đầu mà ở đó mình hoặc là lấy thằng bên trái cùng, hoặc là lấy thằng bên phải cùng. Vậy phát biểu lại, ta có bài toán con: Nếu xét trên một hàng đợi hai đầu gồm \(n\) phần tử \(\{a_1, a_2, \dots, a_{n-1}, a_n\}\) và ta chỉ được lấy thằng bên trái cùng hoặc bên phải cùng rồi xóa nó và nhận số diểm tối ưu. Hãy tính giá trị tối đa của \(X - Y\) với \(X, Y\) là số số lẻ thằng thứ hai và thứ nhất lấy được khi cả 2 đứa chơi tối ưu.

\(\color{#c01515}{\text{1. Approach <Bài toán con tương tự>}}\)

  • \(\color{#f03030}{\text{<Bài toán con>}}\) Với bài toán con về hàng đợi hai đầu và tính \(max(X - Y)\), bạn đọc có thể tham khảo ở đây

  • \(\color{#f03030}{\text{<Nhận xét nhỏ>}}\) Ta chỉ quan tâm các số lẻ nên sẽ biến cả mảng \(a[]\) thành \((a_i = 1)\) khi \(a_i\) lẻ và \((a_i = 0)\) khi \(a_i\) chẵn. Điều này không ảnh hưởng bài toán nhưng lại có lợi trong việc tính toán

  • \(\color{#f03030}{\text{<Bài toán chính>}}\) Ta sẽ nhân đôi mảng thành dãy \(A[] = \{a_1, a_2, \dots, a_{n - 1}, a_n, a_1, a_2, \dots, a_{n - 1}, a_n\}\)

Tại mỗi vị trí \(p\)\(\{p \in \mathbb{N}, p \in [0, n)\ \}\) ta có được đoạn \(A[p + 1\dots p + n)\) là hàng đợi hai đầu sau khi bỏ \(a_p\) và duỗi ra

Ta sẽ xử lí từng đoạn con \(A[p + 1\dots p + n)\) của \(N\) ván chơi bằng deque trong \(O(n^2)\)

Gọi \(v[]\) là mảng nén của dãy \(A[]\) dựa theo Phép toán với ngăn xếp hai đầu

Ta có thể tính được \(d = X - Y\) từ mảng \(v[]\) và đếm được số lượng số lẻ trong đoạn \(v[]\)\(t = X + Y\)

Gọi \(E\) là tính chẵn lẻ số bị bỏ đi, \(e = a[p + n - 1]\)

Từ \(d\)\(t\) ta tính được \(X = \frac{t - d}{2}\)\(Y = \frac{t + d}{2}\)

Nếu \(X + E > Y\) thì ta tăng biến đếm, là khi ông chủ thắng


\(\color{#009933}{\text{1. Code tham khảo<Accepted> }}\): CTDL, Deque, Tham lam, Concept, Two-pointers

\(^{^{\color{#7f5f3f}{\text{Complexity : }} O(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;
        x &= 1;
    }

    a.resize(n << 1);
    for (int i = 0; i < n; ++i) a[i + n] = a[i];
    vector<int> v(n - 1, false);

    int res = 0;
    for (int p = 0; p < n; ++p)
    {
        int m = n - 1;
        for (int i = 0, j = p; i < m; ++i)
        {
            v[i] = a[j++];
            for (; i >= 2 && v[i - 2] <= v[i - 1] && v[i - 1] >= v[i]; i -= 2, m -= 2)
                v[i - 2] = (v[i - 2] + v[i] - v[i - 1]);
        }

        int t = 0;
        for (int i = p; i < p + n - 1; ++i)
            if (a[i]) t++;

        int d = 0;
        for (int l = 0, r = m - 1, t = 1; l <= r; t = -t)
            d += t * (v[l] > v[r] ? v[l++] : v[r--]);

        int e = a[n - 1 + p];
        int x = (t - d) / 2;
        int y = (t + d) / 2;
        if (x + e > y) res++;
    }

    cout << res;
    return 0;
}

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

  • Ta nhân đôi mảng và biến dãy về dãy nhị phân tương ứng vói tính chẵn lẻ của nó để tiện tính toán

  • Ta sẽ xét trường hợp đặc biệt khi \(L = R\) và trường hợp thường khi \(L < R\) để suy ra công thức quy hoạch động

  • Sau đó, khi có được \(f[L][R]\) là cách chơi tối ưu đoạn \([L; R]\), ta duyệt qua từng \(p\) thỏa \(\{p \in \mathbb{N}, p \in [0, n)\ \}\)

Vì nước đi đầu người chơi đầu chọn \(a_p\) nên sau đó người thứ hai chơi tối ưu \(f[L][R]\) với \([L; R] = [p + 1; p + n - 1]\)


\(\color{#c01515}{\text{2. Approach}}\)

  • \(\color{#f03030}{\text{Nhận xét :}}\)

Ta chỉ quan tâm các số lẻ nên sẽ biến cả mảng \(a[]\) thành \((a_i = 1)\) khi \(a_i\) lẻ và \((a_i = 0)\) khi \(a_i\) chẵn. Điều này không ảnh hưởng bài toán nhưng lại có lợi trong việc tính toán

Khi bỏ phần tử \(a_p\), ta có thể duỗi hình tròn thành một hàng đợi hai chiều. Nên để tiện tính toán ta sẽ nhân đôi mảng thành dãy \(A[] = \{a_1, a_2, \dots, a_{n - 1}, a_n, a_1, a_2, \dots, a_{n - 1}, a_n\}\). Tại mỗi vị trí \(p\)\(\{p \in \mathbb{N}, p \in [0, n)\ \}\) ta có được đoạn \(A[p + 1\dots p + n)\)

  • \(\color{#f03030}{\text{Trường hợp đặc biệt: }}\)

Khi \(L = R\) thì \(f[L][R] = A[L]\)

\(a[i] = a[i + n]\) nên \(f[L][R] = f[L + n][R + n]\) \(\forall L = R\)

  • \(\color{#f03030}{\text{Với các trường hợp thường: }}\)

Khi \(L < R\) thì ta có 2 lựa chọn, hoặc là lấy \(a[L]\) và đối thủ chơi tối ưu đoạn còn lại \(f[L + 1][R]\), ,hoặc là lấy \(a[R]\) và đối thủ chơi tối ưu đoạn còn lại \(F[L][R - 1]\)

Vậy ta sẽ chọn giá trị tối đa của 2 cách trên là nước đi tối ưu: \(f[L][R] = max(a[L] - f[L + 1][R], a[R] - f[L][R - 1])\)

  • \(\color{#f03030}{\text{Duyệt các N ván chơi}}\)

\(D = a_p - f[p + 1][p + n - 1]\) là hiệu số lượng sổ lẻ người đầu - người thứ hai lấy được

Khi \(D > 0\) thì tăng biến đếm (người đầu lấy nhiều số lẻ hơn)


\(\color{#009933}{\text{2. Code tham khảo<Accepted> }}\): Quy hoạch động

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

C++
const int INF = 1e9;
int main()
{
    int n;
    cin >> n;

    vector<int> a(n << 1);
    vector<vector<int> > f(n << 1, vector<int>(n << 1, -INF));
    for (int i = 0; i < n; ++i)
    {
        int x;
        cin >> x;
        a[i]    = a[n + i]        = x & 1;
        f[i][i] = f[n + i][n + i] = x & 1;
    }

    for (int d = 1; d < n; ++d) 
        for (int l = 0, r = d; r < a.size(); ++l, ++r)
            f[l][r] = max(a[l] - f[l + 1][r], a[r] - f[l][r - 1]);

    int res = 0;
    for (int i = 0; i < n; ++i)
    {
        int l = i + 1, r = i + n - 1;
        if (a[i] - f[l][r] > 0) res++;
    }

    cout << res;
    return 0;
}


Comments

There are no comments at the moment.