## Dãy số chẵn lẻ đẹp ```cpp= #include<bits/stdc++.h> using namespace std; using ll=long long; using pii=pair<int, int>; using pll=pair<ll, ll>; using vi=vector<int>; using vll=vector<ll>; struct FenwickTree { vector<ll> bit; int n; FenwickTree(int n) { this->n = n; bit.assign(n, 0); } ll sum(int idx) { ll ret = 0; for (; idx >= 0; idx = (idx & (idx + 1)) - 1) ret += bit[idx]; return ret; } ll sum(int l, int r) { return sum(r) - sum(l - 1); } void add(int idx, int delta) { for (; idx < n; idx = idx | (idx + 1)) bit[idx] += delta; } }; int n, k, a[200005]; ll b[200005], ps[200005]; FenwickTree fwt(200005); int main() { #define name "DAYSOCHANLEDEP" if(fopen(name".inp", "r")){ freopen(name".inp", "r", stdin); freopen(name".out", "w", stdout); } ios::sync_with_stdio(false); cin.tie(0); cin >> n >> k; for (int i=1; i<=n; i++) { cin >> a[i]; ps[i] = ps[i-1]; if (a[i]&1) ps[i] -= a[i]; else ps[i] += a[i]; b[i] = ps[i]; } sort(b, b+n+1); int p=1; ll ans=0; for (int i=1; i<=n; i++) { while (a[p]%2!=a[i]%2) { int pos = lower_bound(b, b+n+1, ps[p-1]) - b; fwt.add(pos, 1); ++p; } int l = lower_bound(b, b+n+1, ps[i]-k) - b; int r = upper_bound(b, b+n+1, ps[i]) - b - 1; ans += fwt.sum(l, r); } cout << ans; return 0; } ``` ## FastFood ```cpp= #include<bits/stdc++.h> using namespace std; const int N = 1501; vector<int> cnt[N*N]; int n; int b[N*N]; int sum[N*N]; int ans[2*N]; int main() { ios_base::sync_with_stdio(false); cin.tie(0); cin >> n; for (int i=1; i<=n; i++) { int val; for (int j=1; j<=n; j++) { cin >> val; cnt[val].push_back(j); } } for (int i=1; i<=n*n; i++) { int res = 0; int sz = cnt[i].size(); for (int j=1; j<=sz; j++) b[j] = 1e9; for (int j=0; j<sz; j++) { int u = cnt[i][j]; int pos = upper_bound (b,b+sz+1,u) - b; sum[j] = pos; b[pos] = u; } res = 0; for (int j=1; j<=sz; j++) b[j] = 1e9; for (int j=(int) sz-1; j>=0; j--) { int u = n-cnt[i][j]+1; int pos = upper_bound (b,b+sz+1,u) - b; b[pos] = u; ans[sum[j]+pos-1]++; } } for (int i=1; i<=2*n-1; i++) cout << ans[i] << '\n'; } ``` ## abc Giờ ta sẽ đi tìm lần lượt độ dài lớn nhất của đoạn con mà nó chứa quá bán kí tự $a$, $b$ rồi $c$. Giả sử ta đang giải bài toán với kí tự $a$ chiếm quá bán: - Đặt $b_i = 1$ nếu kí tự $i$ là $a$, ngược lại bằng $-1$. - Bài toán lúc này quy về **tìm đoạn con có tổng dương dài nhất**. Các bạn có thể giải trong $O(n \times log)$, tham khảo tại [đây](https://www.geeksforgeeks.org/longest-subarray-with-sum-greater-than-equal-to-zero/) (bài tham khảo là tổng không âm). <details> <summary>Code</summary> ```cpp= #include<bits/stdc++.h> using namespace std; #define int long long const int N = 1e5+5; string s; int n; int a[N],f[N]; int solve (char c) { int ans = 0; for (int i=1; i<=n; i++) { a[i] = a[i-1]; if (s[i] == c) a[i] += 1; else a[i] -= 1; int l = 1, r = i, res = i; f[i] = min (f[i-1],a[i]); while (r >= l) { int mid = (r-l+1); if (a[i] - f[mid] > 0) { res = mid; r = mid - 1; } else l = mid + 1; } ans = max (ans,i-res); } return ans; } void go () { cin >> n >> s; s = "?" + s; int res = max ({solve('a'), solve('b'), solve ('c')}); cout << res << endl; } signed main() { ios_base::sync_with_stdio(false); cin.tie(0); go(); } ``` </details> ## Biển số xe Ta sẽ chia ra $3$ biến lưu $3$ phần của biển số: - Phần $2$ số đầu tiên - Phần chữ cái - Phần $7$ số cuối cùng Sau đó, ta sẽ cộng dần kết quả vào phần $7$ chữ số cuối cùng. Để đơn giản, ta cứ cộng lần lượt một lượng vừa đủ để $7$ chữ số cuối chạm mốc $10^7$, sau đó gán giá trị phần cuối cùng là $0$ và tăng chữ cái lên, nếu chữ cái đang là $‘Z’$ thì chuyển qua $‘A’$ và tăng phần đầu tiên lên $1$ đơn vị. Nếu $K$ không còn đủ lớn để cộng như vậy, ta cộng thẳng $K$ vào $7$ chữ số cuối cùng và kết thúc quá trình. <details> <summary>Code</summary> ```cpp= #include<bits/stdc++.h> using namespace std; #define int long long void print (int n, int k) { vector<int> v; while (n) v.push_back(n%10), n /= 10; while (v.size() < k) v.push_back(0); reverse(v.begin(),v.end()); for (auto u : v) cout << u; } void solve () { string s; cin >> s; int k; cin >> k; int dau = (s[0]-'0')*10 + s[1] - '0'; char giua = s[2]; int cuoi = 0; for (int i=3; i<=9; i++) cuoi = cuoi*10 + s[i] - '0'; int full = 1e7; while (k) { int need = full - cuoi; if (need > k) { cuoi += k; break; } else { cuoi = 0; k -= need; if (giua == 'Z') { giua = 'A'; dau++; } else { giua = char (giua + 1); } } } print (dau,2); cout << giua; print (cuoi,7); cout << endl; } signed main() { ios_base::sync_with_stdio(false); cin.tie(0); solve(); } ``` </details> ## CApp Do $a_i \le 2*10^5$ nên ta có thể dùng đếm phân phối. Gọi $cnt_a$ là số lần giá trị $a$ xuất hiện trong đoạn đang xét hiện tại. Gọi $res$ là kết quả tốt nhất hiện tại, $cur$ là giá trị $cnt(l,r)$ hiện tại. Ta có hai thao tác: - $add(a):$ $cnt_a++$, nếu $cnt_a = x$ thì $cur++$. - $del(b):$ nếu $cnt_b = x$ thì $cur--$. Sau đó $cnt_b--$. Ta thực hiện $add(a_i)$ với $i \in [1,k]$, lúc này ta sẽ có $cur$ là kết quả của đoạn $[1,k]$. Cập nhật $res$ với $cur$. Để chuyển lên tính đoạn $[2,k+1]$, ta $del(a_1)$ đi và $add(a_{k+1})$ vào, lúc này biến $cur$ thành kết quả của đoạn $[2,k+1]$. Cập nhật $res$ với $cur$. Cứ dịch như vậy tới khi xét hết tất cả các đoạn. Độ phức tạp: $O(n)$. <details> <summary>Code</summary> ```cpp= #include<bits/stdc++.h> using namespace std; #define int long long const int N = 2e5+5; int n,k,x; int a[N],cnt[N]; int cur = 0; void del (int val) { if (cnt[val] == x) cur--; cnt[val]--; } void add (int val) { cnt[val]++; if (cnt[val] == x) cur++; } namespace trau { void solve () { int res = 0; for (int i=1; i<=n-k+1; i++) { int cur = 0; memset (cnt,0,sizeof(cnt)); for (int j=i; j<=i+k-1; j++) { cnt[a[j]]++; if (cnt[a[j]] == x) cur++; } res = max (res,cur); } cout << res ; } } void solve () { memset (cnt,0,sizeof(cnt)); cin >> n >> k >> x; for (int i=1; i<=n; i++) { cin >> a[i]; } int res = 0; cur = 0; for (int i=1; i<=k; i++) { add (a[i]); } res = cur; for (int i=k+1; i<=n; i++) { del(a[i-k]); add(a[i]); res = max (res,cur); } cout << res << endl; } signed main() { ios_base::sync_with_stdio(false); cin.tie(0); solve(); } ``` </details> ## MaxMod Duyệt phân tập, gọi tập $a$ là những tổng đã sinh ra được từ nửa đầu, tập $b$ là tập những tổng đã sinh ra được từ nửa cuối. Với mỗi phần tử trong tập $a$ hoặc $b$, ta lấy phần dư của nó với $m$. Sau đó, $sort$ các tập này lại. Gọi phần tử đang xét ở tập $a$ là $u$, ta cần tìm một giá trị $v$ ở tập $b$ sao cho $(u+v)\%m$ là lớn nhất. Do $u,v < m$, vậy nên ta chỉ cần tìm phần tử $v$ sao cho $v$ lớn nhất và bé hơn hoặc bằng $m-u-1$. Bởi nếu ta lấy quá giá trị này, lúc đó $u+v \ge m$, và do $v < m$ nên $(u+v)\%m < u$, như vậy ta có thể lấy riêng tập $u$ sẽ tối ưu hơn. Việc tìm $v$ có thể thực hiện bằng tìm kiếm nhị phân. <details> <summary>Code</summary> ```cpp= #include<bits/stdc++.h> using namespace std; #define int long long int n,m; vector<int> a,b; const int N = 45; int c[N]; void ql_first(int id, int sum) { if (id == n/2 + 1) { a.push_back(sum); return; } ql_first(id+1,sum); ql_first(id+1,(sum+c[id])%m); } void ql_second (int id, int sum) { if (id == n + 1) { b.push_back(sum); return; } ql_second(id+1,sum); ql_second(id+1,(sum+c[id])%m); } signed main() { ios_base::sync_with_stdio(false); cin.tie(0); cin >> n >> m; for (int i=1; i<=n; i++) { cin >> c[i]; } a.push_back(0); b.push_back(0); ql_first(1,0); ql_second(n/2+1,0); sort (a.begin(),a.end()); sort (b.begin(),b.end()); int ans = 0; for (auto u : a) { int id = upper_bound (b.begin(),b.end(),m-u-1) - b.begin() - 1; ans = max (ans,u + b[id]); } cout << ans << '\n'; } ``` </details> ## LCMAX Ta nhận thấy rằng, ta sẽ luôn chọn tách $n$ ra thành tổng của các lũy thừa của số nguyên tố phân biệt để tối ưu nhất, lưu ý rằng ta không nhất thiết cần chúng phải có tổng bằng đúng $n$, bởi vì ta có thêm bù các số $1$ vào mà không ảnh hưởng tới kết quả. Gọi $dp(i,j)$ là bội chung lớn nhất thu được để tạo ra số bằng $i$ và chỉ dùng $j$ số nguyên tố đầu tiên. Tới lúc này ta chỉ cần duyệt số mũ (rất nhỏ) nữa để chuyển trạng thái. <details> <summary>Code</summary> ```cpp= #include<bits/stdc++.h> using namespace std; #define int unsigned long long int n; const int N = 350; int f[N][N]; int p[N]; int cnt[N]; int m; void sieve () { for (int i=1; i<=350; i++) p[i] = 1; for (int i=2; i <= 350; i++) { if (p[i]) { for (int j=i*i; j<=350; j+=i) p[j] = 0; cnt[++m] = i; } } } int dak; void solve () { cin >> n; f[0][0] = 1; for (int i=0; i<=m; i++) f[0][i] = 1; for (int i=1; i<=n; i++) { for (int j=1; j<=m; j++) { f[i][j] = max (f[i][j-1],f[i-1][j]); for (int k=cnt[j]; k<=i; k=k*cnt[j]){ f[i][j] = max (f[i-k][j-1]*k,f[i][j]); } } } cout << f[n][m] << '\n'; } signed main() { sieve(); int t; t = 1; while (t--) solve(); } ``` </details>