# Prefix Sum - Tối ưu bộ nhớ ## Kiến thức cần biết - **Prefix Sum** cơ bản - Tăng các phần tử từ đoạn $l$ đến $r$ trong $O(1)$ dùng **Prefix Sum** - **Unordered_map** hoặc **Map** - **Vector** hoặc **Mảng Tĩnh** ## Khái quát ### Bài toán: Cho $n$ số nguyên dương $a[i]$ và $t$ truy vấn, mỗi truy vấn cho ba số nguyên $l, r, x$, cần tăng các phần tử trong đoạn $l$ đến đoạn $r$ lên $x$ đơn vị. In ra $n$ số nguyên dương $a[i]$ sau $t$ truy vấn? - $1 <= l <= r <= n <= 10^5$ - $1 <= t <= 10^5$ - $1 <= x, a[i] <= 10^9$ ### Lời giải: Ta tạo một mảng $S$ gồm $n$ phần tử, với mỗi truy vấn, ta tăng $S[l]$ lên x đơn vị và giảm $S[r + 1]$ đi x đơn vị. Sau các truy vấn, ta chạy $i$ ($1 <= i <= n$) và đặt $S[i] += S[i - 1]$. Từ đây muốn suy ra mảng $a$, với mỗi phần tử thứ $i$ thì giá trị của nó sau khi cập nhật là $a[i] + S[i]$. ```python #Code mẫu, có thể ko ac xd n, t = map(int, input().split()) a = [0] + list(map(int, input().split())) S = [0] * (n + 2) for _ in range(t): l, r, x = map(int, input().split()) S[l] += x S[r + 1] -= x for i in range(1, n + 1): S[i] += S[i - 1] for i in range(1, n + 1): print(a[i] + S[i], end = " ") ``` ## Tầng nhà (THTB Sơn Trà 2022) ### Đề bài: https://ibb.co/RDm8x1C ### Lời giải: #### Subtask 1 & 2 (1 <= a[i] <= 10^6): Ta tạo ra mảng $S$ gồm $10^6$ phần tử, với $S[i]$ là số lượng toà nhà cô độc của ngày thứ $i$. Xét từng toà nhà thứ $i$, kiểm tra xem nó có thể cô độc vào một ngày nào đó không ($a[i - 1] < a[i] > a[i + 1]$). Nếu có, ta tính toán ra $l$ (là ngày đầu tiên toà nhà đó cô độc) và $r$ (là ngày cuối cùng toà nhà đó còn cô độc) của toà nhà đó. **Công thức:** - $l = max(a[i - 1], a[i + 1]) + 1$ - $r = a[i]$ Với mỗi $l$ và $r$ của toà nhà thứ $i$, ta tăng $S[l]$ lên $1$ và $S[r + 1]$ xuống $1$, áp dụng bài toán mở đầu. Sau khi xét tất cả các toà nhà, ta chạy $i$ ($1 <= i <= n$) và đặt $S[i] += S[i - 1]$. Kết quả sẽ là số lớn nhất của các $S[i]$ ```c++ //Không copy code ll n, a[100005], mx = 0, kq, S[1000006]; //Mảng S là lưu các ngày int main(){ ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++){ if (a[i - 1] < a[i] && a[i] > a[i + 1]){ //Nếu toà thứ i có thể đơn độc S[max(a[i - 1], a[i + 1]) + 1] += 1; S[a[i] + 1] -= 1; //Áp dụng bài toán mở đầu } } for (int i = 1; i <= 1e6; i++) S[i] += S[i - 1]; //Áp dụng bài toán mở đầu for (int i = 0; i <= 1e6; i++) mx = max(mx, S[i]); //Tìm max của các ngày cout << mx; } ``` #### Subtask 3 (1 <= a[i] <= 10^18): **Nhận xét:** Không cần lưu hết các ngày của mảng $S$, chỉ cần lưu các ngày then chốt. Vậy các ngày then chốt là những ngày nào??? Ở code cho **Subtask 1 & 2**, cụ thể là **2 cái if đầu và cái for thứ 2**, ta chỉ cập nhật các ngày của mảng $S$ có dạng $a[i] + 1$, là ngày mà toà thứ $i$ chìm. Tại sao chỉ lưu các ngày có dạng $a[i]+1$, vì các ngày đó sẽ có thể có sự kiện đặc biệt như số lượng toà cô độc tăng lên hay giảm xuống. ```python Ví dụ: S[max(a[i - 1], a[i + 1]) + 1] += 1; thì có dạng S[a[i] + 1] += 1; S[a[n] + 1] -= 1; thì có dạng S[a[i] + 1] -= 1; S[a[2] + 1] += 1; thì có dạng S[a[i] + 1] += 1; ``` Từ đó ta chỉ cần lưu các ngày $a[i] + 1$ ở trong mảng $S$ ($1 <= i <= n$). Dùng **unordered_map** (có thể dùng **map** nhưng sẽ chậm hơn)! Tạo một **vector** $al$ (viết tắt của $all$) là các ngày then chốt của mình. ```c++ //Không copy code ll n, a[100005], mx = 0; unordered_map <ll, ll> S; //Tạo mảng S dùng unordered_map thay cho mảng tĩnh vector <ll> al; //Vector lưu các ngày then chốt unordered_map <ll, bool> check; //Kiểm tra xem ngày đó đã được lưu chưa int main(){ ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); cin >> n; for (int i = 1; i <= n; i++){ cin >> a[i]; if (!check[a[i] + 1]){ //Kiểm tra xem ngày a[i] + 1 đã lưu chưa al.push_back(a[i] + 1); check[a[i] + 1] = true; } } for (int i = 1; i <= n; i++){ if (a[i - 1] < a[i] && a[i] > a[i + 1]){ //Nếu toà thứ i có thể đơn độc S[max(a[i - 1], a[i + 1]) + 1] += 1; S[a[i] + 1] -= 1; //Áp dụng bài toán mở đầu } } sort(al.begin(), al.end()); //Sắp xếp lại, chuẩn bị để Prefix Sum for (int i = 1; i < al.size(); i++) S[al[i]] += S[al[i - 1]]; //Áp dụng bài toán mở đầu for (ll i : al) mx = max(mx, S[i]); //Tìm max của các ngày cout << mx; } ``` ## Vấn đề kĩ năng? Khi chúng ta dùng **unordered_map**, hoặc **map**, thì sẽ ảnh hưởng tới tốc độ quyết của chương trình, vậy phải làm thế nào? ### 2051E: Best Price Đề bài: https://codeforces.com/contest/2051/problem/E ```c++ const int N = 2e5 + 5, limit = 2e9; int t, n, k, a[N + 1], b[N + 1]; vector<int> all; map<int, bool> check; void them(int x){ if (!check[x]){ check[x] = true; all.push_back(x); } } int main(){ ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); cin >> t; while (t--){ cin >> n >> k; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) cin >> b[i]; all.clear(); check.clear(); them(1); them(limit); map<int, int> mua; map<int, int> niga; for (int i = 1; i <= n; i++){ mua[1]++; mua[b[i] + 1]--; niga[a[i] + 1]++; niga[b[i] + 1]--; them(a[i]); them(b[i]); them(a[i] + 1); them(b[i] + 1); } sort(all.begin(), all.end()); int vt = 0; ll mx = 0; for (int i = 1; i < all.size(); i++) mua[all[i]] += mua[all[i - 1]], niga[all[i]] += niga[all[i - 1]]; for (int i = 0; i < all.size(); i++){ ll tong = mua[all[i]] * (ll)all[i]; int nx = niga[all[i]]; if (nx <= k){ if (tong > mx){ mx = tong; vt = all[i]; } } } cout << mx << "\n"; } } ``` Cách trên không sai, nhưng mà quá lạm dụng cấu trúc dữ liệu gây quá thời gian. Giải pháp là sẽ dùng **vector**. Hãy xem code dưới đây ```c++ const int N = 2e5 + 5, limit = 2e9; int t, n, k, a[N + 1], b[N + 1]; vector<int> all; map<int, bool> check; vector<wtf> query1, query2; void them(int x){ if (!check[x]){ check[x] = true; all.push_back(x); } } void muadi(int x, int val){ query1.push_back({x, val}); } void danhgia(int x, int val){ query2.push_back({x, val}); } int main(){ ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); cin >> t; while (t--){ cin >> n >> k; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) cin >> b[i]; all.clear(); check.clear(); query1.clear(); query2.clear(); them(1); them(limit); for (int i = 1; i <= n; i++){ /* mua[1]++; mua[b[i] + 1]--; niga[a[i] + 1]++; niga[b[i] + 1]--; */ muadi(1, 1); muadi(b[i] + 1, -1); danhgia(a[i] + 1, 1); danhgia(b[i] + 1, -1); them(a[i]); them(b[i]); them(a[i] + 1); them(b[i] + 1); } sort(all.begin(), all.end()); sort(query1.begin(), query1.end()); sort(query2.begin(), query2.end()); vector<ll> mua(all.size(), 0), niga(all.size(), 0); for (int i = 0, j = 0; i < query1.size(); i++){ while (all[j] != query1[i].fi) j++; mua[j] += query1[i].se; } for (int i = 0, j = 0; i < query2.size(); i++){ while (all[j] != query2[i].fi) j++; niga[j] += query2[i].se; } for (int i = 1; i < all.size(); i++) mua[i] += mua[i - 1], niga[i] += niga[i - 1]; ll mx = 0; for (int i = 0; i < all.size(); i++){ ll tong = mua[i] * (ll)all[i]; int nx = niga[i]; if (nx <= k) mx = max(mx, tong); } cout << mx << "\n"; } } ``` Thao tác cập nhật trên đoạn , thì ta sẽ biến thành các truy vấn và hai con trỏ, chỉ cần dùng 1 map để biết được xem giá trị này đã được duyệt chưa.