Prefix Sum

Cấu trúc dữ liệu và giải thuật

5.0 (4 đánh giá)
Tạo bởi huulam3011 Cập nhật lần cuối 3 năm trước 22.727 lượt xem 3 bình luận
Tác giả/Dịch giả: huulam3011 K9
Học nhanh

Danh sách bài học

Prefix Sum

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:

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, a≤ 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, a2a3, …, 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: aai + 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 ssi-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

 Saa+ …+ ai-1 a= (aa+ … + ai-1) + a= Si-1 a

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, a≤ 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, hSk,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-1Si-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 likeshare để ủ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.


K9

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é!

Đánh giá

5.0
5 đánh giá
Đánh giá
5
5
4
3
2
1
Dentis đã đánh giá 23 ngày trước

habaohoccode đã đánh giá 2 tháng trước

vietdung đã đánh giá 2 năm trước

vietdung đã đánh giá 2 năm trước

Floki2021 đã đánh giá 3 năm trước

Bình luận

Để bình luận, bạn cần đăng nhập bằng tài khoản Howkteam.

Đăng nhập
hungtrn75 đã bình luận 24 ngày trước

mình gộp phần đọc data và tính prefix-sum vào làm 1 luôn
 

for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            cin >> s[i][j];
            s[i][j] += s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1];
        }
    }

 

Ankk.__ đã bình luận 2 năm trước

e muốn hỏi trong While(t--)

vậy t-- có ý nghĩa gì ??

 

Không có video.