# LQDOJ CUP 2022 - Round 6 - SOLDIER - Editorial **Author:** Nguyen Huu Nhat Quang - Le Quy Don High School for Gifted Students - Da Nang **Tóm tắt đề:** Cho một dãy các phần tử $a_1, a_2, ..., a_n$ và một số nguyên dương $k$ ($n, k \leq 5000$). Một phần tử $a_i$ được xem là quan trọng khi và chỉ khi tồn tại một tập các phần tử thuộc dãy $a$ có chứa $a_i$, tạm gọi các phần tử này là $b_1, b_2, ..., b_j$, sao cho $b_1+b_2+...+b_j \ge k$, và $b_1+b_2+...+b_j - a_i<k$. Hỏi từng phần tử trong dãy $a$ có quan trọng hay không. ## Nhận xét 1: Những phần tử $a_i\ge k$ luôn quan trọng. Bởi vì xét tập hợp $\{a_i\}$, ta có $a_i \ge k$, nhưng $a_i - a_i = 0 < k$. ## Nhận xét 2: $(b_1+b_2+...+b_j \ge k)\ \&\&\ (b_1+b_2+...+b_j - a_i<k) \Leftrightarrow k - a_i \le b_1+b_2+...+b_j - a_i < k$. ## Ý tưởng đầu tiên: Với mỗi phần tử của dãy $a$ mà $\le k$, kiểm tra xem phần tử đó có quan trọng hay không bằng cách quy hoạch động. Ký hiệu $dp[s] = true/false$ thể hiện việc có thể tạo được một tập hợp có giá trị $s$ hay không. - $dp[0] = true$. - với mỗi $j$ tăng dần từ $1$ đến $n$, $s$ giảm dần từ $n$ về $0$, $j \neq i$, $dp[s] = true$ $\Rightarrow$ $dp[s+a_j] = true$. - Kiểm tra các giá trị $dp[j]$ với $k-a_i\le j < k$. Độ phức tạp: $O(n^3)$, có thể tối ưu thành $O(\frac{n^3}{32})$ khi sử dụng bitset. ### Code: $(O(\frac{n^3}{32}),\ 91/100)$: ```cpp=1 #pragma GCC Optimize ("Ofast") #include<iostream> #include<bitset> #include<vector> #include<algorithm> using namespace std; vector<int>A, B, ind, ans; int n, k; bool check(long long d) { if(d == A.size()) return true; if(d == -1) return false; bitset<10000> dp; dp.set(0); for(int i = 0; i < A.size(); i ++) if(i != d) dp |= (dp << (A[i])); for(int i = k - A[d]; i < k; i ++) if(dp.test(i)) return true; return false; } int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); freopen("SOLDIER.inp", "r", stdin); freopen("SOLDIER.out", "w", stdout); cin >> n >> k; int t; for(int i = 1; i <= n; i ++) { cin >> t; B.push_back(t); if(t >= k) ans.push_back(1); else { ans.push_back(0); A.push_back(t); ind.push_back(i); } } for(int i = 0; i < A.size(); i ++) if(check(i)) ans[ind[i] - 1] = 1; for(int i = 0; i < n; i ++) cout << ans[i]; } ``` ## Nhận xét 3: Nếu $a_i$ quan trọng mà $a_j \ge a_i$, chắc chắn $a_j$ quan trọng. *Chứng minh:* Do $a_i$ quan trọng nên hiển nhiên tồn tại một tập $S$ sao cho tổng các phần tử trong tập $S$, ký hiệu là $d$, không nhỏ hơn $k$, đồng thời $d-a_i<k$. Xét các trường hợp sau: - Trường hợp 1: $S$ không chứa $a_j$: lúc này ta thay $a_i$ trong $S$ thành $a_j$, được một tập $S'$ mới có tổng băng $d-a_i+a_j\ge d\ge k$. Sau khi loại bỏ $a_j$ khỏi tập $S'$, tập mới thu được có tổng bằng $d-a_i<k$. Vậy $a_j$ quan trọng. - Trường hợp 2: $S$ chứa $a_j$: ta loại bỏ $a_j$ khỏi tập $S$ và thu được một tập mới có tổng bằng $d-a_j\le d-a_i <k$. Vậy $a_j$ cũng quan trọng. Sau khi có nhận xét này, ta thấy rằng thay vì phải xét tất cả các số như ý tưởng đầu tiên, ta có thể chặt nhị phân. Độ phức tạp: $O(n^2\times log_2(n))$, có thể tối ưu thành $O(\frac{n^2\times log_2(n)}{32})$ khi sử dụng bitset. ### Code $(O(\frac{n^2\times log_2(n)}{32}), \ 100/100)$: (best submission tại thời điểm 10 giờ 48 phút sáng ngày 6/12/2022) ```cpp=1 #pragma GCC Optimize ("Ofast") #include<iostream> #include<bitset> #include<vector> #include<algorithm> using namespace std; vector<int>A, B, ind, ans; int n, k; bool cmp(int a, int b) { return B[a - 1] < B[b - 1]; } bool check(long long d) { if(d == A.size()) return true; if(d == -1) return false; bitset<10000> dp; dp.set(0); for(int i = 0; i < A.size(); i ++) if(i != d) dp |= (dp << (A[i])); for(int i = k - A[d]; i < k; i ++) if(dp.test(i)) return true; return false; } int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); freopen("SOLDIER.inp", "r", stdin); freopen("SOLDIER.out", "w", stdout); cin >> n >> k; int t; for(int i = 1; i <= n; i ++) { cin >> t; B.push_back(t); if(t >= k) ans.push_back(1); else { ans.push_back(0); A.push_back(t); ind.push_back(i); } } sort(A.begin(), A.end()); sort(ind.begin(), ind.end(), cmp); int l = 0, r = A.size(), m; while(l < r) { m = (l + r) / 2 + 1; if(check(m - 1) == false) l = m; else r = m - 1; } l --; for(int i = l + 1; i < A.size(); i ++) ans[ind[i] - 1] = 1; for(int i = 0; i < n; i ++) cout << ans[i]; } ```