
Danh sách bài học
Prefix Sum
Nội dung bài viết Học nhanh
Trong các bài học trước, chúng ta đã cùng nhau tìm hiểu về một số cấu trúc dữ liệu. Các cấu trúc dữ liệu đó được gọi chung là cấu trúc dữ liệu lưu trữ. Ngày hôm nay, chúng ta sẽ cùng nhau đi tìm hiểu về cấu trúc dữ liệu dạng truy vấn đầu tiên là Prefix Sum.
Nội dung
Để có thể hiểu được bài học này một cách tốt nhất, các bạn nên có kiến thức cơ bản về các phần:
- Biến, kiểu dữ liệu, toán tử trong C++
- Câu điều kiện, vòng lặp, hàm trong C++
- Mảng trong C++
- Các kiến thức cần thiết để theo dõi khóa học
- Nhập, xuất dữ liệu qua file trong C++
Trong bài học này chúng ta sẽ tìm hiểu về:
- Khái niệm Prefix Sum
- Cách ứng dụng
Prefix Sum
Bài toán đặt ra
Ta có một bài toán như sau:
Cho một dãy n số nguyên dương ai (n ≤ 105, ai ≤ 109). Có t truy vấn t ≤ 106, mỗi truy vấn gồm hai số nguyên dương i và j yêu cầu in ra tổng của các số trong đoạn ai, ai+1, …, aj.
Ví dụ:
Lời giải ban đầu
Theo suy nghĩ thông thường, đề bài bảo gì ta sẽ làm đó. Do đó, với mỗi một truy vấn, ta sẽ tiến hành duyệt từ i đến j để tính được tổng các số trong đoạn đó.
Từ suy nghĩ đó, ta có thể triển khai code như sau:
Tuy nhiên, ta thấy ở thuật toán trên, với mỗi truy vấn thì ta phải duyệt lại dãy để tính tổng. Do đó, độ phức tạp sẽ là O(t * n). Với n≤ 105 và t ≤ 106 thì thuật toán này là chưa đủ tối ưu. Vậy thì liệu có cách tiếp cận nào khác không?
Nhận xét
Hãy thử cùng nhau suy nghĩ bằng một ví dụ trong thực tế. Chúng ta có một dải băng kích thước 1×n được chia thành n ô có kích thước 1×1. Hãy tìm cách cắt dải băng để lấy được đoạn [i, j] (màu xanh).
Vậy thì chúng ta sẽ làm như thế nào?
Đầu tiên, ta sẽ cắt đoạn [j + 1, n] (màu đỏ) để lấy được đoạn [1, j].
Tiếp theo, ta sẽ cắt đoạn [1, i – 1] (màu đỏ) để thu được đoạn từ [i, j].
Từ ví dụ trên, ta nghĩ để tính tổng các số trong đoạn [i, j], ta sẽ tính tổng các số trong đoạn [1, j] rồi trừ đi tổng các số trong đoạn [1, i – 1].
Thật vậy, hãy xét một dãy số a1, a2, a3, …, an-1, an .
Gọi Si là tổng các số trong dãy trên từ vị trí 1 đến vị trí i.
Ta thấy rằng: ai + ai + 1 + ai + 2 +…+ aj = (a1 + a2 +…+ aj) - (a1 + a2+…+ ai - 1)= Sj - Si-1
Mặt khác, ta lại thấy rằng các giá trị Si hoàn toàn có thể được tính toán trước và truy vấn trong O(1). Do đó, ta nghĩ đến việc sẽ tính toán trước các Si. Si ở đây chính là Prefix Sum (tổng tiền tố).
Cách giải cải tiến
Từ những nhận xét phía bên trên, ta sẽ đưa ra một thuật toán như sau:
- Tiến hành tính toán các Si với Si là tổng các số trong dãy trên từ vị trí 1 đến vị trí i.
- Với mỗi truy vấn, in ra sj - si-1
Câu hỏi bây giờ sẽ là: Tính toán Si bằng cách nào để hiệu quả? Ta thấy
Si = a1 + a2 + …+ ai-1 + ai = (a1 + a2 + … + ai-1) + ai = Si-1 + ai
Do đó, ta có thể dùng một vòng lặp, tính toán Si dựa vào Si-1.
Từ toàn bộ những nhận xét trên, ta có đoạn code sau:
Khi này, mỗi truy vấn ta sẽ thực hiện trong O(1) nên độ phức tạp giảm xuống chỉ còn là O(n + t).
Qua ví dụ trên, ta có thể hiểu đơn giản: Prefix Sum (tổng tiền tố) là một cách sử dụng mảng linh hoạt để tính toán giá trị từ vị trí gốc đến vị trí yêu cầu.
Prefix Sum với mảng hai chiều
Bài toán đặt ra
Tiếp theo, mình sẽ có một bài toán mở rộng cho bài toán ở trên như sau:
Cho một mảng hai chiều n hàng và m cột gồm các số nguyên dương ai (n, m ≤ 103, ai ≤ 109) với ô trên cùng bên trái là ô (1, 1) và ô dưới cùng bên phải là ô (n, m). Có t truy vấn t ≤ 106, mỗi truy vấn gồm 4 số nguyên dương i, j, k, h yêu cầu in ra tổng các ô trong hình chữ nhật có góc trái trên là ô (i, j), góc phải dưới là ô (k, h).
Ví dụ:
Giải thích ví dụ:
Ở truy vấn 1, ta cần in ra tổng các ô tử (1, 1) đến (4, 5) có nghĩa là các ô trong vùng khoanh đỏ
Ở truy vấn 2, ta cần in ra tổng các ô tử (2, 3) đến (4, 4) có nghĩa là các ô trong vùng khoanh đỏ
Ở truy vấn 3, ta cần in ra tổng các ô tử (3, 1) đến (3, 4) có nghĩa là các ô trong vùng khoanh đỏ
Với các ý tưởng tương tự phần trước, liệu các bạn có thể tìm ra cách làm cho bài toán này không? Trước khi đọc phần tiếp theo để biết đáp án, hãy thử dừng lại để suy nghĩ một chút nhé.
Ý tưởng
Hãy tưởng tượng, chúng ta có một tấm bìa có kích thước n×m được chia làm n×m ô vuông nhỏ có kích thước 1×1. Giờ ta sẽ cần cắt để thu được vùng màu xanh như hình:
Bây giờ, hãy cùng nhau xem làm sao để có thể thu được phần
màu xanh đó nhé. Ta sẽ gọi ô trên trái của vùng màu xanh là ô (i, j).
Đầu tiên, ta sẽ cắt bỏ hình chữ nhật có ô trên trái là (1, 1) và ô phải dưới là (i – 1, m) (màu đỏ)
Tiếp theo, ta sẽ cắt bỏ hình chữ nhật có ô trên trái là (1, 1) và ô phải dưới là (n, j - 1) (màu cam).
Lúc này ta sẽ thấy có một vùng chống lấn bị cắt 2 lần (màu tím). Hãy nhớ đến vùng này vì ở phần sau ta sẽ cần chú ý đến nó.
Sau 2 bước, ta đã thu được vùng màu xanh mà ta muốn. Nhìn vào quá trình vừa rồi, các bạn đã nhìn ra lời giải chưa?
Lời giải
Gọi Si, j là tổng của các ô trong hình chữ nhật có ô trái trên là (1, 1) và ô phải dưới là (i, j).
Để có thể tính tổng các ô trong hình chữ nhật có góc trái trên là ô (i, j), góc phải dưới là (k, h) ta sẽ lấy Sk, h (bảng gốc) trừ đi Si-1,h (vùng đỏ) và Sk, j-1 (vùng cam). Tuy nhiên, ta đã trừ đi hai lần giá trị Si-1, j-1 (vùng tím) do đó ta sẽ cần cộng bù vùng này.
Tóm lại, ta sẽ có tổng các ô trong hình chữ nhật có góc trái trên là ô (i, j), góc phải dưới là (k, h) là sk,h - Si-1, h- Sk,j-1 + Si-1,j-1.
Bây giờ lại đặt ra vấn đề, ta sẽ tính si, j như thế nào?
Hãy giả sử ô màu xanh là ô (i, j) và hiện tại ta đang cần tính si, j. Ta chỉ cần biết tổng của toàn bộ các ô màu trắng rồi cộng với ô (i, j) là sẽ tính được si, j.
Ta thấy Si-1, j (màu vàng) và Si, j-1 (màu xanh dương) khi cộng lại sẽ bao gồm tất cả các ô màu trắng.
Tuy nhiên, hai vùng này chồng lấn lên nhau ở vùng màu đỏ, chính là si-1,j-1 đo đó sẽ cần trừ bỏ vùng này để đảm bảo kết quả chính xác.
Tóm lại, ta sẽ có: Si,j =Si-1, j + Si, j-1- Si-1, j-1 + ai, j
Từ toàn bộ các nhận xét trên, ta có đoạn code sau:
Kết luận
Qua bài này chúng ta đã nắm được về Prefix Sum
Bài sau chúng ta sẽ bắt đầu tìm hiểu về thuật toán, mở đầu là Đệ quy
Cảm ơn các bạn đã theo dõi bài viết. Hãy để lại bình luận hoặc góp ý của mình để phát triển bài viết tốt hơn. Đừng quên “Luyện tập – Thử thách – Không ngại khó”.
Tải xuống
Tài liệu
Nhằm phục vụ mục đích học tập Offline của cộng đồng, Kteam hỗ trợ tính năng lưu trữ nội dung bài học Prefix Sum dưới dạng file PDF trong link bên dưới.
Ngoài ra, bạn cũng có thể tìm thấy các tài liệu được đóng góp từ cộng đồng ở mục TÀI LIỆU trên thư viện Howkteam.com
Đừng quên like và share để ủng hộ Kteam và tác giả nhé!

Thảo luận
Nếu bạn có bất kỳ khó khăn hay thắc mắc gì về khóa học, đừng ngần ngại đặt câu hỏi trong phần bên dưới hoặc trong mục HỎI & ĐÁP trên thư viện Howkteam.com để nhận được sự hỗ trợ từ cộng đồng.
Tác giả/Dịch giả

Mình là Nguyễn Hữu Lâm, một người có niềm đam mê rất lớn đối với lập trình. Hiện tại, mình đang là sinh viên Khoa học máy tính của Đại học Bách Khoa Hà Nội. Mong muốn của mình là có thể chia sẻ những kiến thức mà bản thân có cho mọi người, học hỏi, kết bạn với tất cả những người có cùng đam mê với mình.

Nhà sáng lập Howkteam.com, KQuiz.vn & tác giả các khóa học C#, Auto, Unity3D, Python....
Với mong muốn mang đến kiến thức chất lượng, miễn phí cho mọi người, với tâm huyết phá bỏ rào cản kiến thức từ việc giáo dục thu phí. Tôi đã cùng đội ngũ Kteam đã lập nên trang website này để thế giới phẳng hơn.
Hãy cùng chúng tôi lan tỏa kiến thức đến cộng đồng!
Khóa học
Cấu trúc dữ liệu và giải thuật
Bạn đã từng đau đầu với các cấu trúc stack, queue,.. hoặc cảm thấy cực kỳ khó khăn với các thuật toán sắp xếp, tìm kiếm được sử dụng trong lập trình. Đừng lo lắng! Trong khoá học này, chúng ta sẽ cùng nhau tìm hiểu một cách đơn giản nhất về cấu trúc dữ liệu và giải thuật, cũng như giúp bạn nắm rõ hơn về các kiến thức này.
Hãy cùng xem cấu trúc dữ liệu và giải thuật có gì đáng sợ không nhé!
mình gộp phần đọc data và tính prefix-sum vào làm 1 luôn
e muốn hỏi trong While(t--)
vậy t-- có ý nghĩa gì ??