Quét đường (Scanning Line)
Dẫn nhập
Thuật toán quét (scanning line, hay còn gọi là "quét đường thẳng" hoặc "scanline") thường được sử dụng trong các bài toán hình học. Đúng như tên gọi, ý tưởng là dùng một đường thẳng "quét" qua toàn bộ hình, thường để giải các bài toán về diện tích, chu vi, hoặc đếm số điểm trong hình hai chiều.
Bài toán hợp diện tích hình chữ nhật trong mặt phẳng
Trên hệ tọa độ hai chiều, cho nhiều hình chữ nhật với tọa độ góc dưới trái và góc trên phải, hãy tính tổng diện tích hợp của tất cả các hình chữ nhật này.
Quy trình
Quan sát hình vẽ, tổng diện tích có thể tính vét cạn, nhưng nếu dữ liệu lớn thì cần dùng thuật toán quét.
Giả sử ta có một đường thẳng quét từ dưới lên trên:
Như hình, mỗi lần quét, ta chia mặt phẳng thành các dải màu khác nhau, mỗi dải có chiều cao là khoảng cách quét, còn chiều rộng thay đổi liên tục.
Gán nhãn cho mỗi cạnh ngang của hình chữ nhật: cạnh dưới gán \(+1\), cạnh trên gán \(-1\). Mỗi khi gặp một cạnh ngang, ta cộng/trừ giá trị này vào đoạn tương ứng trên trục hoành.
Lưu ý
Thao tác này giống như duyệt một chuỗi ngoặc: mở ngoặc cộng 1, đóng ngoặc trừ 1, "giá trị" tại mỗi vị trí chính là độ sâu hiện tại, kiểm tra giá trị lớn hơn 0 tương đương với việc đang nằm trong một cặp ngoặc, tức là đoạn này thuộc về hình chữ nhật.
Chiều rộng của các dải nhỏ (có thể có nhiều dải) chính là tổng độ dài các đoạn trên trục hoành có giá trị lớn hơn 0.
Cài đặt
Dùng cây đoạn (segment tree) để duy trì tổng độ dài các đoạn trên trục hoành có số lần phủ lớn hơn 0.
Yêu cầu:
- Cộng/trừ 1 cho một đoạn.
- Tính tổng độ dài các đoạn có giá trị lớn hơn 0 trên toàn trục.
Nếu bạn thử dùng cây đoạn thông thường, có thể gặp khó khăn: khi cập nhật một đoạn, dù biết đoạn đó trùng với nút quản lý, ta vẫn không thể biết ngay số đoạn chuyển từ 1 về 0 (hoặc ngược lại).
Giải pháp là: với mỗi nút, duy trì số lần phủ hoàn toàn (v[], giống như lazy tag không cần đẩy xuống) và tổng độ dài đã phủ (w[]).
Cần rời rạc hóa.
LuoGu P5490【Mẫu】Quét & Hợp diện tích hình chữ nhật - Mã mẫu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
[POJ 1151 Atlantis] (http://poj.org/problem?id=1151) - Mã mẫu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
Bài tập luyện tập
- POJ1177 Picture
- POJ3832 Posters
- LuoGu P1856 [IOI1998][USACO5.5] Chu vi hình chữ nhật Picture
- Độ dài cạnh ngang là biến thiên độ dài phủ.
- Tính hai chiều riêng biệt để tránh phải xét cạnh dọc.
- Khi sắp xếp các cạnh, chú ý trường hợp trùng cạnh.
- Nếu dữ liệu nhỏ, có thể mô phỏng vét cạn \(O(n^2)\).
Hình hộp trực giao B chiều
Hình hộp trực giao B chiều là tập các điểm trong không gian \(B\) chiều, với mỗi chiều \(i\) có tọa độ nằm trong đoạn \([l_i, r_i]\).
Thông thường, đoạn 1 chiều gọi là "đoạn", 2 chiều gọi là "hình chữ nhật", 3 chiều gọi là "hình hộp" (bài toán đếm điểm trong hình chữ nhật 2D là bài toán hình hộp trực giao 2 chiều).
Với bài toán tĩnh hai chiều, ta có thể dùng quét một chiều, cấu trúc dữ liệu duy trì chiều còn lại. Khi quét từ trái sang phải, các thao tác cập nhật và truy vấn sẽ diễn ra trên chiều còn lại.
Nếu truy vấn có thể hiệu (dạng prefix sum), dùng cây Fenwick (BIT) hoặc segment tree; nếu không, dùng phân chia và chinh phục (CDQ divide and conquer, nhưng ở đây không đề cập).
Một cách khác là nhìn bài toán dưới góc độ dãy số: quét theo điểm kết thúc \(r=1\cdots n\), duy trì cấu trúc dữ liệu cho các điểm bắt đầu \(l\), tức là quét một chiều, cấu trúc dữ liệu duy trì chiều còn lại.
Độ phức tạp thường là \(O((n+m)\log n)\).
Đếm điểm trong hình chữ nhật 2D
Cho một dãy số độ dài \(n\), có \(m\) truy vấn, mỗi truy vấn hỏi có bao nhiêu phần tử trong đoạn \([l,r]\) có giá trị thuộc \([x,y]\).
Bài toán này gọi là "đếm điểm trong hình chữ nhật 2D". Thực chất là đếm số điểm trong một hình chữ nhật trên mặt phẳng.
Cách đơn giản nhất là dùng quét + cây Fenwick (BIT).
Vì đây là bài toán tĩnh 2D, ta dùng quét để chuyển thành bài toán động 1D, rồi dùng cấu trúc dữ liệu cho dãy số.
Trước hết, rời rạc hóa các truy vấn, dùng BIT duy trì số lượng phần tử theo giá trị. Với mỗi truy vấn \(l, r\), khi quét đến \(l-1\) thì đếm số phần tử trong \([x,y]\) là \(a\), khi quét đến \(r\) thì đếm được \(b\), đáp án là \(b-a\).
Bài tập ví dụ
LuoGu P2163 [SHOI2007] Nỗi phiền của người làm vườn
Đầu tiên rời rạc hóa. Gọi \(ans_{x, y}\) là số điểm trong hình chữ nhật có góc dưới trái \((0,0)\), góc trên phải \((x,y)\). Đáp án truy vấn là \(ans_{c, d} - ans_{a - 1, d} - ans_{c, b - 1} + ans_{a - 1, b - 1}\).
Mã mẫu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | |
LuoGu P1908 Đếm số nghịch thế
Đúng vậy, đếm số nghịch thế cũng có thể dùng tư tưởng quét. Chuyển bài toán thành: duyệt từ cuối về đầu, với mỗi vị trí \(i\), đếm số phần tử trong \([i+1,n]\) nhỏ hơn \(a_i\). Vì giá trị có thể lớn, cần rời rạc hóa. Duyệt từ cuối về đầu, mỗi lần cập nhật BIT, rồi đếm số phần tử nhỏ hơn \(a_i\) (tức là số nghịch thế của \(a_i\)). Có thể dùng BIT hoặc segment tree để cập nhật và truy vấn.
Mã mẫu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
LuoGu P1972 [SDOI2009] Chuỗi hạt của HH
Tóm tắt: Cho một dãy số, nhiều truy vấn hỏi trong đoạn \([l,r]\) có bao nhiêu số khác nhau.
Có thể suy luận tính chất, rồi dùng quét theo điểm kết thúc, cấu trúc dữ liệu duy trì điểm bắt đầu, hoặc chuyển về bài toán đếm điểm trong hình chữ nhật 2D.
Với mỗi \(a_i\), gọi \(pre_i\) là vị trí xuất hiện trước đó của \(a_i\) (nếu chưa từng xuất hiện thì \(pre_i=0\)). Theo đề, mỗi số chỉ tính một lần trong đoạn, nên chỉ cần đếm số \(pre_x \le l-1\) trong đoạn \([l,r]\).
Bài toán trở thành: cho dãy \(pre\), nhiều truy vấn hỏi trong đoạn \([l,r]\) có bao nhiêu \(pre_i \le l-1\).
Xem \(pre_i\) là điểm trên mặt phẳng: hoành độ \(i\), tung độ \(pre_i\), mỗi truy vấn là đếm số điểm trong hình chữ nhật có góc dưới trái \((l,0)\), góc trên phải \((r,l-1)\).
Vì truy vấn có thể hiệu, ta tách thành hai hình chữ nhật: \((0,0)-(r,l-1)\) trừ \((0,0)-(l-1,l-1)\). Như vậy dễ dùng quét.
Mỗi thao tác \(O(\log n)\), tổng \(O((n+m)\log n)\).
Mã mẫu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
Bài tập luyện tập
- LuoGu P8593「KDOI-02」Một phát bắn - ứng dụng đếm nghịch thế.
- AcWing 4709. Bộ ba - phiên bản đơn giản hóa, cũng là ứng dụng đếm nghịch thế.
- LuoGu P8773 [Blue Bridge Cup 2022 Prov A] Chọn số XOR - biến thể của chuỗi hạt HH.
- LuoGu P8844 [Trí Tuệ Cup #4 Sơ loại] Tiểu Kha và lá rụng - bài toán trên cây chuyển về dãy số rồi đếm điểm 2D.
Tóm lại, tư tưởng chính của đếm điểm trong hình chữ nhật 2D là: dùng cấu trúc dữ liệu duy trì một chiều, quét qua chiều còn lại.
Tài liệu tham khảo
- cnblogs/Yang1208: Giải thích scanline, segment tree mở động
- csdn/riba2534: POJ1151 Atlantis giải chi tiết
- csdn/刀刀狗 0102: POJ1151 Atlantis giải chi tiết
- Bàn về scanline
Last updated on this page:, Update history
Found an error? Want to help improve? Edit this page on GitHub!
Contributors to this page:OI-wiki
All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply