--- title Quy hoạch động (Phần 2) - Lời giải --- Quy hoạch động (Phần 2) - Lời giải === --- ###### ✍️ Author: 2School Guideline ###### 📋 Content: [TOC] --- # [CSES - Towers](https://cses.fi/problemset/task/1073) ## Đề bài - Cho $n$ khối lập phương có kích thước lần lượt là $k_1, k_2, k_3, ..., k_n. - Một tòa thấp là dãy khối lập phương (có thể không liên tiếp) được chọn phải thỏa mãn các khối lập phương liền kề nhau được chọn khối ở dưới **lớn hơn** khối ở trên. - Đếm số dãy ít nhất cần sử dụng để ghép $n$ khối lại phương lại thành các tòa tháp. ### Input - Dòng đầu gồm số nguyên dương $n$ là số khối lập phương ($n \leq 2 \cdot 10^5$). - Dòng tiếp theo gồm $n$ số nguyên dương là kích thước của $n$ khối lập phương : $k_1, k_2, k_3, ..., k_n$ ($k_i \leq 10^9$). ### Output - Gồm $1$ số nguyên dương duy nhất là số tòa tháp ít nhất. ### Sample Input ``` 5 3 8 2 1 5 ``` ### Sample Output ``` 2 ``` ### Giải thích - Tòa tháp thứ nhất gồm $3$ khối lập phương có kích thước : $3 \ 2 \ 1$. - Tòa tháp thứ hai gồm $2$ khối lập phương có kích thước : $8 \ 5$. ## Lời giải :::spoiler **Ý tưởng** - Đầu gọi dãy $b$ gồm $k$ số nguyên dương được **sắp xếp** không giảm là giá trị cuối cùng của các tòa tháp đã xây : $b_1 < b_2 < b_3 < ... < b_k$. - Khi xét tới khối lập phương thứ $i$, nếu tồn tại vị trí $j$ sao cho $b_j > a_i$ mà $j$ là nhỏ nhất thì ta sẽ thay thế $b_j = a_i$ tức là ta thêm $a_i$ vào chồng thứ $j$. Ngược lại ta thêm vào dãy $b$ khối lập phương $a_i$ (tức là không thể ghép $a_i$ với bất kì tòa tháp nào). Vì sao cách này lại hiệu quả và chính xác ? - **Chứng minh :** Nhận thấy là nếu tồn tại nhiều vị trí $j$ sao cho $b_j > a_i$ thì việc lựa chọn lung tung hay chọn $j$ **lớn** đồng nghĩa với việc **sự tối ưu** sẽ bị mất đi, khiến cho những giá trị $a_{i + 1}, a_{i + 2}, ..., a_n$ có thể sẽ sử dụng nhiều tòa tháp hơn. Vì vậy nếu tồn tại thì ta sẽ chọn vị trí $j$ nhỏ nhất để **ít ảnh hưởng** nhất vào đáp án cho các khối lập phương $a_{i + 1}, a_{i + 2}, ..., a_n$. - Độ phức tạp : $O(n \log n)$. ::: :::spoiler **Cài đặt** ```cpp= #include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; int a[n + 1]; for(int i = 1; i <= n; ++i) cin >> a[i]; vector<int> b; for(int i = 1; i <= n; ++i) { if(b.empty() or b.back() <= a[i]) b.push_back(a[i]); else { int position = upper_bound(b.begin(), b.end(), a[i]) - b.begin(); b[position] = a[i]; } } cout << (int)b.size(); return 0; } ``` ::: # WAVIO (Không có judge) ## Đề bài - Cho dãy số nguyên gồm $n$ số nguyên $a_1, a_2, a_3, ..., a_n$. Một dãy con $b_1, b_2, b_3, ..., b_k$ của dãy $a$ được gọi là **gợn sóng** khi tồn tại vị trí $m$ sao cho $b_1 < b_2 < b_3 < ... < b_m > b_{m + 1} > b_{m + 2} > ... > b_k$. Tìm dãy **gợn sóng** có đồ dài lớn nhất là dãy con (có thể không liên tiếp) của dãy $a$. ### Input - Dòng đầu gồm số nguyên dương $n$ ($n \leq 10^3$). - Dòng tiếp theo gồm $n$ số nguyên : $a_1, a_2, a_3, ..., a_n$ ($-10^9 \leq a_i \leq 10^9$). ### Output - Gồm $1$ số nguyên dương duy nhất là dãy **gợn sóng** dài nhất. ### Sample Input ``` 6 2 1 3 2 5 3 ``` ### Sample Output ``` 4 ``` ### Giải thích - Dãy **gợn sóng** dài nhất có độ dài $4$ là $1 \ 3 \ 5 \ 3$, một dãy khác có thể là $2 \ 3 \ 5 \ 3$. ## Lời giải :::spoiler **Ý tưởng** Gọi : - $f_i$ là dãy con tăng dài nhất khi xét từ $1$ đến $i$ sao cho phần tử cuối cùng kết thúc tại $i$. - $g_i$ là dãy con tăng dài nhất khi xét từ $n$ trở về $i$ sao cho phần tử cuối cùng kết thúc tại $i$. - Nếu điểm $m$ đặt tại $i$ thì đáp án sẽ là $f_i + g_i - 1$ ($-1$ vì lặp lại vị trí $i$ $2$ lần). Tới đây ta xử lý $2$ bài toán con một cách độc lập vói độ phức tạp $O(n^2)$. ::: :::spoiler **Cài đặt** ```cpp= #include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; int a[n + 1]; for (int i = 1; i <= n; ++i) cin >> a[i]; int f[n + 1]; for (int i = 1; i <= n; ++i) { int mx = 0; for(int j = 1; j < i; ++j) if(a[j] < a[i]) mx = max(mx, f[j]); f[i] = mx + 1; } int g[n + 1]; for(int i = n; i >= 1; --i) { int mx = 0; for(int j = i + 1; j <= n; ++j) if(a[j] < a[i]) mx = max(mx, g[j]); g[i] = mx + 1; } int ans = 0; for(int i = 1; i <= n; ++i) ans = max(ans, f[i] + g[i] - 1); cout << ans; return 0; } ``` :::