---
# System prepended metadata

title: Prefix Sum - Tối ưu bộ nhớ

---

# 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.