# DELETE ![image](https://hackmd.io/_uploads/ryNEo-voR.png) ## SUBTACK **SUBTACK 1:** $n \le 20$ **SUBTACK 2:** $n \le 10^3$ **SUBTACK 3:** $n \le 10^6$ ## SOLUTION CỦA DELETE ### SUB 1: Sử dụng **bitmask** để vét tất cả các trường hợp. :::success :::spoiler CODE MẪU ```cpp= #include <bits/stdc++.h> #define bit(mask, i) ((mask >> i) & 1) #define BIT(i) (1 << i) using namespace std; const int maxn = 1e6 + 10; const int MOD = 1e9 + 7; int n, a[maxn], ans = 0; bool check(int mask) { vector<int> v; for (int i = 0; i < n; i++) if (bit(mask, i)) v.push_back(a[i]); if (v.size() < 3) return 0; if (v.front() != 1 || v.back() != 3) return 0; for (int i = 1; i < v.size() - 1; i++) if (v[i] != 2) return 0; return 1; } int main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); freopen("DELETE.INP", "r", stdin); freopen("DELETE_TRAU.OUT", "w", stdout); cin >> n; for (int i = 0; i < n; i++) cin >> a[i]; for (int mask = 0; mask < BIT(n); mask++) { ans += check(mask); ans %= MOD; } cout << ans; } ``` ::: **ĐỘ PHỨC TẠP LÀ:** $O(2^n)$ ### SUB 2: Sử dụng quy hoạch động để tối ưu. Gọi $dp[i]$ là số cách chọn dãy thỏa mãn yêu cầu kết thúc tại $i$ $\Rightarrow$ Kết quả của bài toán là tổng các $dp[i]$ với $a[i] = 3$ Ban đầu khởi tạo các giá trị $dp[i] = 0$ với mọi $i$ thỏa mãn $1 \le i \le n$ Công thức truy hồi: - Theo đề ta có: Nếu $a[i] = 1$ thì phần tử thứ $i$ chỉ được xuất hiện 1 lần ở đầu dãy. Nói cách khác, chả cả phần tử nào đứng trước phần tử thứ $i$ được vì $i$ là thằng đầu tiên $\Rightarrow$ $dp[i] = 1$ với $a[i] = 1$ - Tiếp theo, nếu $a[i] = 2$ thì phần tử thứ $i$ có thể nối với phần tử có giá trị $1$ hoặc các phần tử có giá trị $2$ $\Rightarrow$ $dp[i] = dp[i] + dp[j]$ với $a[i] = 2$ và $a[j] = 1$ hoặc $a[j] = 2$ - Cuối cùng, nếu $a[i] = 3$ thì phần tử thứ $i$ chỉ có thể nối với phần tử có giá trị $2$ để tạo nên dãy đúng $\Rightarrow dp[i] = dp[i] + dp[j]$ với $a[i] = 3$ và $a[j] = 2$ :::success :::spoiler CODE MẪU ```cpp= #include <bits/stdc++.h> using namespace std; const int maxn = 1e6 + 10; const int MOD = 1e9 + 7; int n, a[maxn], dp[maxn], ans = 0; int main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); freopen("DELETE.INP", "r", stdin); freopen("DELETE_TRAU2.OUT", "w", stdout); cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) { if (a[i] == 1) { dp[i] = 1; continue; } for (int j = 1; j < i; j++) { if (a[i] == 2 && a[i] >= a[j]) dp[i] += dp[j], dp[i] %= MOD; if (a[i] == 3 && a[j] == 2) dp[i] += dp[j], dp[i] %= MOD; } } for (int i = 1; i <= n; i++) if (a[i] == 3) ans += dp[i], ans %= MOD; cout << ans; } ``` ::: **ĐỘ PHÚC TẠP:** $O(n^2)$ ### SUB 3: Vẫn là quy hoạch động nhưng chúng ta sẽ tìm cách tối ưu chiều. Để ý rằng, với mỗi $dp[i]$ chúng ta gọi ở trên chúng ta chỉ cần tìm $1$ giá trị cụ thể rồi cộng vô đáp án. Ở subtack cuối này chúng ta sẽ gọi $dp[i]$ là số lượng dãy có phần tử kết thúc là giá trị $i$. Với cách gọi như vậy thì đáp án của bài toán sẽ là $dp[3]$ (số dãy có phần tử kết thúc mang giá trị $3$) Công thức truy hồi: - Nếu $a[i] = 1$ thì giá trị này sẽ tạo ra thêm 1 dãy kết thúc với giá trị $1$ $\Rightarrow$ $dp[1] = dp[1] + 1$ - Nếu $a[i] = 2$ thì giá trị này sẽ nối với giá trị $1$ để tạo ra thêm $1$ dãy có chiều dài bằng $2$ và giá trị bắt đầu là $1$ và giá trị kết thúc là $2$. Hoặc nối với phần tử có giá trị là $2$ để tạo ra dãy mới vẫn thỏa mãn yêu cầu đề bài $\Rightarrow$ $dp[2] = dp[2] + dp[1] + dp[2]$ - Nếu $a[i] = 3$ thì giá trị này phải đi với phần tử kết thúc có giá trị $2$ $\Rightarrow dp[3] = dp[3] + dp[2]$ :::success :::spoiler CODE MẪU ```cpp= #include <bits/stdc++.h> using namespace std; const int MOD = 1e9 + 7; int n, dp[4]; int main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); freopen("DELETE.INP", "r", stdin); freopen("DELETE.OUT", "w", stdout); cin >> n; for (int i = 1; i <= n; i++) { int x; cin >> x; if (x == 1) dp[1]++; else if (x == 2) { dp[2] += dp[1] + dp[2]; dp[2] %= MOD; } else dp[3] += dp[2], dp[3] %= MOD; } cout << dp[3]; } ``` :::