Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học
Thi thử miễn phí
🏆 Thi thử HSG Tin học tuần này trên ClueOJ
Mở công khai cho mọi người · đề do thầy Nguyễn Việt Nam Sơn biên soạn
Lịch thi, đề cũ & lời giải →

Bài 13: Mảng đánh dấu, đếm phân phối và mảng cộng dồn (prefix sum), mảng hiệu trong C++ và Python

Bài 13 – Mảng đánh dấu, đếm phân phối, cộng dồn
Series: Nhập môn lập trình thuật toán Độ khó: Dễ Bài 13 / Kiến thức nền tảng Mảng đánh dấu, cộng dồn
// Nhập môn lập trình · Bài 13
Mảng đánh dấu, đếm phân phối và mảng cộng dồn (prefix sum): ba kỹ thuật biến O(n²) thành O(n)
Ba mẹo cực đơn giản nhưng xuất hiện trong hàng trăm bài thi: đánh dấu số đã gặp, đếm tần suất bằng mảng, và tính tổng một đoạn bất kỳ chỉ bằng một phép trừ.

Lời mở đầu

Bạn hãy thử đề này: “Cho dãy $n = 10^5$ số và $q = 10^5$ câu hỏi, mỗi câu hỏi tổng các số từ vị trí $l$ tới $r$ là bao nhiêu?”. Cách tự nhiên là với mỗi câu hỏi chạy một vòng lặp cộng từ $l$ tới $r$, tệ nhất $q \cdot n = 10^{10}$ phép tính → TLE chắc chắn (xem bài 9). Với kỹ thuật mảng cộng dồn của bài này, ta chuẩn bị trước mất $O(n)$, sau đó trả lời mỗi câu hỏi chỉ bằng một phép trừ. Tương tự, mảng đánh dấu và đếm phân phối giúp trả lời “số $x$ có xuất hiện không, xuất hiện mấy lần?” trong $O(1)$ thay vì phải dò cả mảng. Đây là các kỹ thuật “rẻ mà mạnh” đầu tiên em nên thuộc lòng.

Trong bài này bạn sẽ học: mảng đánh dấu để kiểm tra sự xuất hiện trong $O(1)$; đếm phân phối (đếm tần suất) và sắp xếp đếm $O(n + V)$; mảng cộng dồn 1 chiều để tính tổng đoạn $[l, r]$ trong $O(1)$; mảng hiệu để cộng một giá trị vào cả đoạn trong $O(1)$; giới thiệu cộng dồn 2 chiều. Có khung chạy tay cho từng kỹ thuật, mô phỏng tổng đoạn bằng mảng cộng dồn và 8 bài tập kèm gợi ý.

Lộ trình bài viết, bấm vào từng phần để nhảy tới:

Phần 1Mảng đánh dấu: số này đã xuất hiện chưa?

Ý tưởng: dùng chính giá trị làm chỉ số. Tạo mảng bool da[MAX] toàn false; mỗi khi đọc được số $x$ thì gán da[x] = true. Sau đó muốn biết $x$ có xuất hiện không, chỉ cần nhìn da[x]: $O(1)$, thay vì dò cả dãy $O(n)$. Điều kiện để dùng được: các giá trị là số nguyên không âm và không quá lớn (cỡ $10^6$ – $10^7$, vì cần một mảng kích thước bằng giá trị lớn nhất). Nếu giá trị lớn tới $10^9$ thì dùng set hoặc map ở bài 11.

Dãy a = 3 0 5 3 1 → mảng đánh dấu da[0..7] true0true1false2true3false4true5false6false7 Hỏi “có số 4 không?” → nhìn da[4] = false → không. Hỏi “có số 5 không?” → da[5] = true → có. Mỗi câu hỏi O(1).

Ví dụ: tìm MEX, tức số nguyên dương nhỏ nhất không xuất hiện trong dãy. Đánh dấu mọi số, rồi đi từ 1 lên, gặp ô false đầu tiên là đáp án. Vì dãy có $n$ số nên đáp án không vượt quá $n + 1$, các số lớn hơn $n$ có thể bỏ qua luôn, nhờ vậy mảng chỉ cần kích thước $n + 2$ dù giá trị lớn cỡ nào.

Code C++ và Python
#include <bits/stdc++.h>
using namespace std;

bool da[1000005];

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++){
        int x; cin >> x;
        if(x >= 1 && x <= n) da[x] = true;
    }
    int mex = 1;
    while(da[mex]) mex++;
    cout << mex;
    return 0;
}
n = int(input())
a = list(map(int, input().split()))
da = [False] * (n + 2)
for x in a:
    if 1 <= x <= n:
        da[x] = True
mex = 1
while da[mex]:
    mex += 1
print(mex)
C++▶ Chạy tay từng dòng codeĐánh dấu da[x] = true cho mọi x trong [1, n], rồi tìm ô false đầu tiên từ 1 trở đi: đó là số nhỏ nhất chưa xuất hiện.
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4bool da[1000005];
5
6int main(){
7 int n;
8 cin >> n;
9 for(int i = 0; i < n; i++){
10 int x; cin >> x;
11 if(x >= 1 && x <= n) da[x] = true;
12 }
13 int mex = 1;
14 while(da[mex]) mex++;
15 cout << mex;
16 return 0;
17}

Phần 2Đếm phân phối: số này xuất hiện mấy lần?

Nâng cấp mảng đánh dấu: thay bool bằng int và thay vì gán true thì tăng thêm 1: dem[x]++. Khi đó dem[x] cho biết số $x$ xuất hiện bao nhiêu lần. Một ứng dụng đẹp là sắp xếp đếm phân phối (counting sort): đếm xong, chỉ cần đi từ giá trị nhỏ tới lớn và in mỗi giá trị $v$ đúng dem[v] lần là được dãy tăng dần! Độ phức tạp $O(n + V)$ với $V$ là giá trị lớn nhất, nhanh hơn cả sort $O(n \log n)$ khi $V$ nhỏ.

Code C++ và Python
#include <bits/stdc++.h>
using namespace std;

int dem[1000005];

int main(){
    int n;
    cin >> n;
    int mx = 0;
    for(int i = 0; i < n; i++){
        int x; cin >> x;
        dem[x]++;
        mx = max(mx, x);
    }
    for(int v = 0; v <= mx; v++)
        for(int k = 0; k < dem[v]; k++)
            cout << v << " ";
    return 0;
}
n = int(input())
a = list(map(int, input().split()))
mx = max(a)
dem = [0] * (mx + 1)
for x in a:
    dem[x] += 1
kq = []
for v in range(mx + 1):
    kq.extend([v] * dem[v])
print(*kq)
C++▶ Chạy tay từng dòng codeSắp xếp đếm phân phối: đếm dem[x]++ cho mọi x, rồi đi v = 0, 1, 2, … và in mỗi v đúng dem[v] lần.
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4int dem[1000005];
5
6int main(){
7 int n;
8 cin >> n;
9 int mx = 0;
10 for(int i = 0; i < n; i++){
11 int x; cin >> x;
12 dem[x]++;
13 mx = max(mx, x);
14 }
15 for(int v = 0; v <= mx; v++)
16 for(int k = 0; k < dem[v]; k++)
17 cout << v << " ";
18 return 0;
19}
Khi nào KHÔNG dùng được mảng đếm?
Giá trị âm: cộng thêm một hằng số để dời về không âm, ví dụ giá trị trong $[-10^6, 10^6]$ thì dùng dem[x + 1000000]. Giá trị quá lớn ($10^9$, $10^{18}$): mảng không đủ bộ nhớ, dùng map hoặc sắp xếp rồi đếm các đoạn bằng nhau. Giá trị không phải số nguyên (số thực, xâu): dùng map. Với chữ cái, mảng dem[26] và chỉ số c - 'a' (xem bài 8) là lựa chọn kinh điển.

Mô phỏng trực quan. Đọc code đếm phân phối xong nhiều bạn vẫn thắc mắc: "không so sánh gì mà sao dãy lại tăng dần được?". Em bấm Tiếp từng bước để thấy mỗi số được "thả" thẳng vào ô có chỉ số bằng chính nó, rồi đọc lại mảng cnt từ trái sang phải nhé.

MÔ PHỎNGSắp xếp bằng đếm phân phối, từng bướcNhập tối đa 14 số từ 0 đến 9. Giai đoạn 1 đếm, giai đoạn 2 in ra theo thứ tự chỉ số.
số đang đọc / ô vừa tăngô cnt đang in rađã vào kết quảđã xử lý / ô bằng 0

Phần 3Mảng cộng dồn: tổng một đoạn trong O(1)

Cho dãy $a_1, a_2, \dots, a_n$ (đánh số từ 1 cho tiện). Định nghĩa mảng cộng dồn (prefix sum) $S_i = a_1 + a_2 + \dots + a_i$ là tổng $i$ phần tử đầu, với $S_0 = 0$. Ta tính được toàn bộ $S$ trong $O(n)$ nhờ công thức $S_i = S_{i-1} + a_i$. Khi đó tổng đoạn từ $l$ tới $r$ bằng “tổng $r$ phần tử đầu” trừ đi “tổng $l - 1$ phần tử đầu”:

a[l] + a[l+1] + … + a[r] = S[r] − S[l − 1]
3a[1]1a[2]4a[3]1a[4]5a[5]9a[6]2a[7]6a[8] S[5] = 3+1+4+1+5 = 14 S[2] = 4 Tổng a[3..5] = S[5] − S[2] = 14 − 4 = 10
Code C++ và Python
#include <bits/stdc++.h>
using namespace std;

long long a[100005], S[100005];

int main(){
    int n, q;
    cin >> n >> q;
    for(int i = 1; i <= n; i++) cin >> a[i];
    S[0] = 0;
    for(int i = 1; i <= n; i++)
        S[i] = S[i - 1] + a[i];
    while(q--){
        int l, r; cin >> l >> r;
        cout << S[r] - S[l - 1] << "\n";
    }
    return 0;
}
import sys
data = sys.stdin.read().split()
n, q = int(data[0]), int(data[1])
a = [0] + list(map(int, data[2:2 + n]))
S = [0] * (n + 1)
for i in range(1, n + 1):
    S[i] = S[i - 1] + a[i]
out, p = [], 2 + n
for _ in range(q):
    l, r = int(data[p]), int(data[p + 1]); p += 2
    out.append(S[r] - S[l - 1])
print('\n'.join(map(str, out)))
C++▶ Chạy tay từng dòng codeXây mảng cộng dồn S[i] = S[i−1] + a[i] trong một lượt duyệt, rồi trả lời mỗi câu hỏi tổng đoạn [l, r] bằng đúng một phép trừ.
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4long long a[100005], S[100005];
5
6int main(){
7 int n, q;
8 cin >> n >> q;
9 for(int i = 1; i <= n; i++) cin >> a[i];
10 S[0] = 0;
11 for(int i = 1; i <= n; i++)
12 S[i] = S[i - 1] + a[i];
13 while(q--){
14 int l, r; cin >> l >> r;
15 cout << S[r] - S[l - 1] << "\n";
16 }
17 return 0;
18}

Thử với dữ liệu của em: nhập dãy và các câu hỏi $(l, r)$, mô phỏng sẽ xây mảng $S$ từng ô rồi cho thấy mỗi câu hỏi chỉ “nhìn” đúng hai ô $S[r]$ và $S[l-1]$. Bộ đếm bên dưới so sánh số phép cộng của cách cộng trực tiếp với cách dùng mảng cộng dồn.

MÔ PHỎNGTổng đoạn bằng mảng cộng dồnNhập dãy (tối đa 12 số) và các câu hỏi, mỗi câu hỏi là hai số l r cách nhau bởi dấu cách, các câu hỏi cách nhau bởi dấu phẩy.
Đang tải mô phỏng…
Mảng cộng dồn 2 chiều (đọc thêm)
Với ma trận ở bài 7, đặt $S[i][j]$ là tổng hình chữ nhật từ ô $(1, 1)$ tới ô $(i, j)$: $S[i][j] = S[i-1][j] + S[i][j-1] - S[i-1][j-1] + a[i][j]$ (trừ đi phần bị cộng hai lần). Tổng hình chữ nhật từ $(x_1, y_1)$ tới $(x_2, y_2)$ là $S[x_2][y_2] - S[x_1-1][y_2] - S[x_2][y_1-1] + S[x_1-1][y_1-1]$, cũng chỉ $O(1)$. Đây là kỹ thuật rất hay gặp trong đề thi về lưới, bản đồ.

Phần 4Mảng hiệu: cộng một giá trị vào cả đoạn trong O(1)

Bài toán ngược lại: có $q$ thao tác, mỗi thao tác cộng $v$ vào mọi phần tử từ $l$ tới $r$; cuối cùng in cả dãy. Cộng trực tiếp tốn $O(n)$ mỗi thao tác. Mẹo mảng hiệu $D$: mỗi thao tác chỉ làm hai việc D[l] += v và D[r + 1] -= v. Sau tất cả thao tác, lấy cộng dồn của $D$ sẽ được đúng lượng đã cộng vào từng vị trí: giá trị $v$ “bật lên” từ vị trí $l$ và bị “tắt đi” từ vị trí $r + 1$. Tổng độ phức tạp $O(n + q)$.

Code C++ và Python
#include <bits/stdc++.h>
using namespace std;

long long D[100005];

int main(){
    int n, q;
    cin >> n >> q;
    while(q--){
        int l, r; long long v;
        cin >> l >> r >> v;
        D[l] += v;
        D[r + 1] -= v;
    }
    long long cur = 0;
    for(int i = 1; i <= n; i++){
        cur += D[i];
        cout << cur << " ";
    }
    return 0;
}
n, q = map(int, input().split())
D = [0] * (n + 2)
for _ in range(q):
    l, r, v = map(int, input().split())
    D[l] += v
    D[r + 1] -= v
cur, kq = 0, []
for i in range(1, n + 1):
    cur += D[i]
    kq.append(cur)
print(*kq)
C++▶ Chạy tay từng dòng codeMảng hiệu: mỗi thao tác chỉ sửa 2 ô D[l] += v, D[r+1] −= v. Cuối cùng cộng dồn D từ trái sang phải được giá trị từng vị trí.
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4long long D[100005];
5
6int main(){
7 int n, q;
8 cin >> n >> q;
9 while(q--){
10 int l, r; long long v;
11 cin >> l >> r >> v;
12 D[l] += v;
13 D[r + 1] -= v;
14 }
15 long long cur = 0;
16 for(int i = 1; i <= n; i++){
17 cur += D[i];
18 cout << cur << " ";
19 }
20 return 0;
21}

Mô phỏng trực quan. Mảng hiệu là phần khó hình dung nhất của bài: chỉ sửa 2 ô mà cả đoạn lại được cộng thêm v. Em xem mỗi truy vấn chỉ chạm vào ô xanh và ô đỏ, rồi đến cuối mới cộng dồn một lần để "hiện hình" mảng a mới. Thử đổi mảng và truy vấn của riêng em nhé.

MÔ PHỎNGMảng hiệu: cộng v vào cả đoạn chỉ bằng 2 phép tínhNhập mảng a (tối đa 10 số) và tối đa 6 truy vấn "l r v" cách nhau bởi dấu ;
d[l] += vd[r+1] −= vđang cộng dồnchưa tính / ô đệm

Bài tậpLuyện đánh dấu, đếm và cộng dồn

Giới hạn chung: $n, q \le 10^5$ (bài 4: $n \le 10^6$), giá trị $0 \le a_i \le 10^6$ trừ khi đề nói khác. Hãy để ý: cách làm “thẳng” nào cũng $O(n \cdot q)$ hoặc $O(n^2)$, đều TLE.

Bài 1Có xuất hiện không?Dễ

Cho dãy $n$ số và $q$ câu hỏi, mỗi câu hỏi một số $x$: in YES nếu $x$ có trong dãy, ngược lại NO.

Input
5
3 8 1 8 6
3
8 2 6
Output
YES
NO
YES
💡 Gợi ý
Mảng bool da[1000005], đánh dấu khi đọc dãy, mỗi câu hỏi in theo da[x]. Tổng $O(n + q)$.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

bool da[1000005];

int main(){
    int n; cin >> n;
    for(int i = 0; i < n; i++){ int x; cin >> x; da[x] = true; }
    int q; cin >> q;
    while(q--){ int x; cin >> x; cout << (da[x] ? "YES" : "NO") << "\n"; }
    return 0;
}
n = int(input())
da = set(map(int, input().split()))
q = int(input())
print('\n'.join("YES" if int(x) in da else "NO" for x in input().split()))
Bài 2Số nguyên dương nhỏ nhất vắng mặtDễ

Cho dãy $n$ số nguyên (có thể âm, có thể lớn tới $10^9$). Tìm số nguyên dương nhỏ nhất không có trong dãy.

Input
6
3 4 1 5 2 7
Output
6
💡 Gợi ý
Đúng bài MEX ở phần 1: chỉ đánh dấu các số trong $[1, n]$, các số khác không ảnh hưởng đáp án (đáp án luôn $\le n + 1$).
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

bool da[100005];

int main(){
    int n; cin >> n;
    for(int i = 0; i < n; i++){ long long x; cin >> x; if(x >= 1 && x <= n) da[x] = true; }
    int m = 1;
    while(da[m]) m++;
    cout << m;
    return 0;
}
n = int(input())
s = set(map(int, input().split()))
m = 1
while m in s:
    m += 1
print(m)
Bài 3Xuất hiện đúng một lầnDễ

In theo thứ tự tăng dần các giá trị xuất hiện đúng một lần trong dãy.

Input
7
4 2 4 9 2 3 1
Output
1 3 9
💡 Gợi ý
Đếm phân phối dem[x]++, rồi duyệt $v$ từ 0 tới giá trị lớn nhất, in những $v$ có dem[v] == 1.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int dem[1000005];

int main(){
    int n; cin >> n;
    int mx = 0;
    for(int i = 0; i < n; i++){ int x; cin >> x; dem[x]++; mx = max(mx, x); }
    for(int v = 0; v <= mx; v++) if(dem[v] == 1) cout << v << " ";
    return 0;
}
from collections import Counter
n = int(input())
c = Counter(map(int, input().split()))
print(*sorted(v for v in c if c[v] == 1))
Bài 4Sắp xếp đếm phân phốiDễ

Cho $n \le 10^6$ số nguyên trong đoạn $[0, 10^6]$. In dãy đã sắp xếp tăng dần.

Input
8
5 3 0 3 9 1 5 3
Output
0 1 3 3 3 5 5 9
💡 Gợi ý
Đúng khung chạy tay ở phần 2. Với $10^6$ số nhớ tăng tốc nhập xuất (bài 9). Đây là bài mà sắp xếp đếm còn nhanh hơn sort.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int dem[1000005];

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int n; cin >> n;
    int mx = 0;
    for(int i = 0; i < n; i++){ int x; cin >> x; dem[x]++; mx = max(mx, x); }
    for(int v = 0; v <= mx; v++)
        for(int k = 0; k < dem[v]; k++) cout << v << ' ';
    return 0;
}
import sys
data = sys.stdin.buffer.read().split()
n = int(data[0]); a = list(map(int, data[1:1 + n]))
mx = max(a)
dem = [0] * (mx + 1)
for x in a:
    dem[x] += 1
out = []
for v in range(mx + 1):
    if dem[v]:
        out.extend([str(v)] * dem[v])
print(' '.join(out))
Bài 5Tổng đoạnDễ

Cho dãy $n$ số nguyên ($|a_i| \le 10^9$) và $q$ câu hỏi $(l, r)$. Với mỗi câu hỏi in $a_l + \dots + a_r$.

Input
5 3
1 2 3 4 5
1 3
2 5
4 4
Output
6
14
4
💡 Gợi ý
Mảng cộng dồn ở phần 3, nhớ dùng long long vì tổng có thể tới $10^{14}$.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

long long S[100005];

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int n, q; cin >> n >> q;
    for(int i = 1; i <= n; i++){ long long x; cin >> x; S[i] = S[i - 1] + x; }
    while(q--){ int l, r; cin >> l >> r; cout << S[r] - S[l - 1] << "\n"; }
    return 0;
}
import sys
d = sys.stdin.read().split()
n, q = int(d[0]), int(d[1])
S = [0] * (n + 1)
for i in range(1, n + 1):
    S[i] = S[i - 1] + int(d[1 + i])
p, out = 2 + n, []
for _ in range(q):
    l, r = int(d[p]), int(d[p + 1]); p += 2
    out.append(S[r] - S[l - 1])
print('\n'.join(map(str, out)))
Bài 6Đoạn k phần tử có tổng lớn nhấtDễ

Cho dãy $n$ số nguyên và $k$ ($1 \le k \le n$). Tìm tổng lớn nhất của $k$ phần tử liên tiếp.

Input
7 3
2 -1 3 5 -2 4 1
Output
7
💡 Gợi ý
Tổng đoạn kết thúc tại $i$ là $S_i - S_{i-k}$. Duyệt $i$ từ $k$ tới $n$, lấy max. $O(n)$ thay vì $O(n \cdot k)$. Chú ý khởi tạo max là một số rất nhỏ (có thể mọi số đều âm).
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

long long S[100005];

int main(){
    int n, k; cin >> n >> k;
    for(int i = 1; i <= n; i++){ long long x; cin >> x; S[i] = S[i - 1] + x; }
    long long best = LLONG_MIN;
    for(int i = k; i <= n; i++) best = max(best, S[i] - S[i - k]);
    cout << best;
    return 0;
}
n, k = map(int, input().split())
a = list(map(int, input().split()))
S = [0]
for x in a:
    S.append(S[-1] + x)
print(max(S[i] - S[i - k] for i in range(k, n + 1)))
Bài 7Đoạn con có tổng chia hết cho kKhá

Cho dãy $n$ số nguyên dương và $k$ ($k \le 10^6$). Đếm số đoạn con liên tiếp $[l, r]$ có tổng chia hết cho $k$.

Input
5 3
1 2 3 4 5
Output
7
💡 Gợi ý
Tổng đoạn $[l, r]$ chia hết cho $k$ khi và chỉ khi $S_r$ và $S_{l-1}$ có cùng số dư khi chia cho $k$. Vậy đếm dem[S_i % k] với mọi $i$ từ 0 tới $n$ (đừng quên $S_0 = 0$); mỗi nhóm $c$ chỉ số cùng số dư tạo ra $\frac{c(c-1)}{2}$ đoạn. Kết hợp cả cộng dồn lẫn đếm phân phối, $O(n + k)$.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

long long dem[1000005];

int main(){
    int n, k; cin >> n >> k;
    long long s = 0, kq = 0;
    dem[0] = 1;                    // S_0 = 0
    for(int i = 1; i <= n; i++){
        long long x; cin >> x;
        s = (s + x) % k;
        kq += dem[s];              // ghép với mọi chỉ số trước đó cùng số dư
        dem[s]++;
    }
    cout << kq;
    return 0;
}
n, k = map(int, input().split())
a = list(map(int, input().split()))
dem = [0] * k
dem[0] = 1
s = kq = 0
for x in a:
    s = (s + x) % k
    kq += dem[s]
    dem[s] += 1
print(kq)
Bài 8Tưới câyTrung bình

Có $n$ cây cao 0. Có $q$ lần tưới, lần thứ $j$ làm các cây từ $l_j$ tới $r_j$ cao thêm $v_j$. In chiều cao cuối cùng của từng cây.

Input
5 3
1 3 2
2 5 1
4 4 5
Output
2 3 3 6 1
💡 Gợi ý
Đúng mảng hiệu ở phần 4: mỗi lần tưới sửa 2 ô, cuối cùng cộng dồn một lượt. $O(n + q)$ thay vì $O(n \cdot q)$.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

long long D[100005];

int main(){
    int n, q; cin >> n >> q;
    while(q--){ int l, r; long long v; cin >> l >> r >> v; D[l] += v; D[r + 1] -= v; }
    long long cur = 0;
    for(int i = 1; i <= n; i++){ cur += D[i]; cout << cur << " "; }
    return 0;
}
n, q = map(int, input().split())
D = [0] * (n + 2)
for _ in range(q):
    l, r, v = map(int, input().split())
    D[l] += v; D[r + 1] -= v
cur, kq = 0, []
for i in range(1, n + 1):
    cur += D[i]; kq.append(cur)
print(*kq)
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇
Bản quyền thuộc về Thầy Nguyễn Việt Nam Sơn. Bài viết, lời giải, hình minh hoạ và mã nguồn trên trang là công sức biên soạn của thầy qua nhiều năm giảng dạy. Vui lòng không sao chép, đăng lại hay sử dụng dưới mọi hình thức khi chưa được sự đồng ý của tác giả. Nếu thấy hữu ích, hãy chia sẻ bằng đường link bài gốc nhé. Cảm ơn bạn!
Tự học thuật toán: khó ở đâu?
Đọc xong một bài giải là bước đầu. Phần lớn học sinh tự học sẽ gặp những trở ngại sau:
  • Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
  • Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
  • Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
  • Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
  • Dễ nản giữa chừng khi không có ai theo sát.
Học cùng thầy Nam Sơn, các trở ngại đó được gỡ bỏ:
  • Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
  • Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
  • Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
  • Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.

Nhận xét