Nhiều bài về đoạn thẳng, khoảng thời gian, hình chữ nhật có lời giải chung: tưởng tượng một đường thẳng quét qua trục từ trái sang phải, chỉ dừng ở các điểm có sự kiện (một đoạn bắt đầu, một đoạn kết thúc, một câu hỏi), và giữ một cấu trúc dữ liệu mô tả những gì đường quét đang cắt. Giữa hai sự kiện liên tiếp, trạng thái không đổi, nên bài toán trên trục liên tục hay tọa độ tới chỉ còn bước. Cái khó là chọn đúng sự kiện, đúng thứ tự xử lý khi trùng tọa độ, và đúng cấu trúc giữ trạng thái.
Sự kiện mở và đóng
Đoạn trên tọa độ nguyên cho hai sự kiện: tại , tại (đoạn nửa mở thì tại ). Sắp mọi sự kiện theo tọa độ; quét qua và cộng dồn, giá trị hiện tại là mức phủ của đoạn từ sự kiện này tới sự kiện kế tiếp. Đây là mảng hiệu (bài 02) khi tọa độ quá lớn để lập mảng: chỉ lưu những điểm có thay đổi. Hai sự kiện cùng tọa độ thì xử lý hết rồi mới đọc mức phủ, hoặc xếp thứ tự theo đúng nghĩa đề (đóng trước mở khi đoạn nửa mở chạm nhau).
Ví dụGiờ Cao Điểm
Đề. khoảng nửa mở , mọi đầu mút khác nhau. Tìm số khoảng cùng phủ một thời điểm nhiều nhất.
Lời giải. Mỗi khoảng sinh hai sự kiện: tại (bắt đầu), tại (kết thúc). Sắp theo thời điểm rồi cộng dồn: sau mỗi sự kiện, tổng đang có là số khoảng đang mở, và đáp án là giá trị lớn nhất của nó.
Bước 1: sinh sự kiện.
ev.push_back({a, 1});
ev.push_back({b, -1});
Bước 2: sắp và quét.
sort(ev.begin(), ev.end());
ll cur = 0, best = 0;
for (pair<ll, ll> e : ev) {
cur += e.second;
best = max(best, cur);
}
Khoảng nửa mở nên khi trùng thời điểm, sự kiện phải đứng trước ; pair sắp trước một cách tự nhiên. Ở bài này các đầu mút khác nhau nên chi tiết đó không phát sinh.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll n;
vector<pair<ll, ll>> ev;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) {
ll a, b;
cin >> a >> b;
ev.push_back({a, 1});
ev.push_back({b, -1});
}
sort(ev.begin(), ev.end());
ll cur = 0, best = 0;
for (pair<ll, ll> e : ev) {
cur += e.second;
best = max(best, cur);
}
cout << best << '\n';
}
4
1 4
2 5
6 8
3 7
3
cho phần sắp.
Ví dụMức Phủ Trên Trục Số
Đề. đoạn đóng, đầu mút nguyên tới . Với mỗi , đếm số tọa độ nguyên được đúng đoạn phủ.
Lời giải. Đoạn đóng trên tọa độ nguyên thành sự kiện tại và tại , để làm việc như đoạn nửa mở. Sau khi xử lý mọi sự kiện tại , mức phủ giữ nguyên cho các tọa độ từ tới trước sự kiện kế tiếp , nên cộng vào ô đáp án thứ . Không cần xử lý riêng sự kiện cùng tọa độ: khoảng giữa chúng dài 0, đóng góp 0 dù mức phủ trung gian là gì.
Bước 1: sự kiện. tới , vẫn vừa ll.
ev.push_back({l, 1});
ev.push_back({r + 1, -1});
Bước 2: cộng độ dài từng khoảng. Sau khi cộng sự kiện thứ , mức phủ cur đúng cho mọi tọa độ từ ev[i] tới trước ev[i + 1].
for (ll i = 0; i + 1 < (ll)ev.size(); i++) {
cur += ev[i].second;
ans[cur] += ev[i + 1].first - ev[i].first;
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 5;
ll n, ans[N];
vector<pair<ll, ll>> ev;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) {
ll l, r;
cin >> l >> r;
ev.push_back({l, 1});
ev.push_back({r + 1, -1});
}
sort(ev.begin(), ev.end());
ll cur = 0;
for (ll i = 0; i + 1 < (ll)ev.size(); i++) {
cur += ev[i].second;
ans[cur] += ev[i + 1].first - ev[i].first;
}
for (ll k = 1; k <= n; k++) cout << ans[k] << ' ';
cout << '\n';
}
4
0 2
2 4
4 4
1 3
1 3 1 0
.
Gán tài nguyên bằng hàng đợi ưu tiên
Muốn gán mỗi khoảng vào một phòng (máy, nhân viên) sao cho hai khoảng chồng nhau không cùng phòng, và dùng ít phòng nhất: quét các khoảng theo thời điểm bắt đầu, giữ hàng đợi ưu tiên các phòng đang dùng theo thời điểm rảnh. Khoảng mới bắt đầu sau khi phòng rảnh sớm nhất đã rảnh thì dùng lại phòng đó, không thì mở phòng mới. Số phòng mở ra bằng mức phủ lớn nhất, cận dưới hiển nhiên, nên là tối ưu.
Hỏi nhanh
Đang có hai phòng rảnh lúc 5 và lúc 9, khoảng mới bắt đầu lúc 7. Thuật toán làm gì?
Phòng rảnh sớm nhất là 5, trước 7, nên dùng lại được. Dùng phòng rảnh lúc 9 là không được vì khoảng mới bắt đầu trước lúc đó. Chỉ mở phòng mới khi cả phòng rảnh sớm nhất cũng chưa rảnh.
Ví dụPhân Phòng Tối Thiểu
Đề. lượt, lượt chiếm các ngày ; hai lượt chung một ngày phải khác phòng. In số phòng ít nhất và phòng của từng lượt.
Lời giải. Xét lượt theo ngày bắt đầu tăng dần. Tới lượt bắt đầu ngày , nếu có phòng đã rảnh trước ngày thì dùng lại, không thì mở phòng mới. Cách này dùng đúng số phòng bằng số lượt cùng chung một ngày nhiều nhất: mỗi lần phải mở phòng mới là lúc mọi phòng đang có đều bận vào ngày . Không thể ít hơn số đó, nên là tối ưu.
Bước 1: sắp chỉ số theo ngày bắt đầu. Giữ chỉ số gốc để in phòng theo thứ tự đầu vào.
sort(id + 1, id + n + 1, cmp);
Bước 2: phòng rảnh sớm nhất. pq chứa các phòng đang dùng dưới dạng (ngày cuối, số phòng), sắp theo ngày cuối tăng dần. Ngày cuối nhỏ hơn hẳn (ngày đóng, nên phải nhỏ hơn hẳn) thì dùng lại phòng đó; ngược lại mở phòng mới. Cuối cùng đẩy lại (ngày cuối của lượt này, phòng).
for (ll t = 1; t <= n; t++) {
ll i = id[t];
if (!pq.empty() && pq.top().first < a[i]) {
room[i] = pq.top().second;
pq.pop();
} else room[i] = ++k;
pq.push({b[i], room[i]});
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 5;
ll n, a[N], b[N], id[N], room[N];
bool cmp(ll x, ll y) {
return a[x] < a[y];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
id[i] = i;
}
sort(id + 1, id + n + 1, cmp);
priority_queue<pair<ll, ll>, vector<pair<ll, ll>>, greater<pair<ll, ll>>> pq;
ll k = 0;
for (ll t = 1; t <= n; t++) {
ll i = id[t];
if (!pq.empty() && pq.top().first < a[i]) {
room[i] = pq.top().second;
pq.pop();
} else room[i] = ++k;
pq.push({b[i], room[i]});
}
cout << k << '\n';
for (ll i = 1; i <= n; i++) cout << room[i] << ' ';
cout << '\n';
}
4
1 2
2 3
4 4
3 5
2
1 2 2 1
.
Quét kèm Fenwick
Khi câu hỏi có hai chiều, quét theo chiều thứ nhất và giữ chiều thứ hai trong Fenwick (bài 16). Đoạn nằm trong khi và : sắp các đoạn theo đầu trái tăng dần (bằng nhau thì đầu phải giảm dần), đoạn nằm trong là đoạn đứng sau nó có đầu phải không quá . Quét từ cuối, Fenwick theo đầu phải đã nén đếm các đoạn đã thêm có đầu phải . Giao điểm giữa đoạn ngang và đoạn dọc: quét theo , đoạn ngang được thêm vào Fenwick theo khi đường quét gặp đầu trái và bị gỡ khi gặp đầu phải; đoạn dọc đếm số đoạn ngang đang có trong khoảng của nó.
Ví dụĐếm Đoạn Bao Chứa
Đề. đoạn khác nhau. Với mỗi đoạn, đếm số đoạn khác nằm trong nó và số đoạn khác chứa nó.
Lời giải. Sắp các đoạn theo tăng, cùng thì giảm. Khi đó đoạn chứa đoạn luôn đứng trước (đầu trái không lớn hơn, cùng đầu trái thì dài hơn), và đoạn nằm trong luôn đứng sau. Đoạn chứa là đoạn đứng trước có ; đoạn nằm trong là đoạn đứng sau có . Hai câu đếm này làm bằng Fenwick theo đã nén, một lần quét xuôi và một lần quét ngược.
Bước 1: nén và sắp. r[i] là hạng của , dùng làm chỉ số Fenwick.
sort(ys + 1, ys + n + 1);
for (ll i = 1; i <= n; i++) r[i] = lower_bound(ys + 1, ys + n + 1, y[i]) - ys;
sort(id + 1, id + n + 1, cmp);
Bước 2: quét xuôi, đếm đoạn chứa. Có đoạn đã thêm; trừ đi những đoạn có nhỏ hơn hẳn (get(r[i] - 1)), vì chúng không chứa , còn lại là số đoạn chứa .
for (ll t = 1; t <= n; t++) {
ll i = id[t];
out[i] = (t - 1) - get(r[i] - 1);
update(r[i], 1);
}
Bước 3: xóa Fenwick, quét ngược, đếm đoạn nằm trong. get(r[i]) là số đoạn đứng sau có .
for (ll i = 1; i <= n; i++) bit[i] = 0;
for (ll t = n; t >= 1; t--) {
ll i = id[t];
in[i] = get(r[i]);
update(r[i], 1);
}
Hai đoạn khác nhau cùng có khác nhau, nên không có trường hợp bằng nhau cần tách.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 5;
ll n, x[N], y[N], id[N], ys[N], r[N], bit[N], in[N], out[N];
bool cmp(ll a, ll b) {
return x[a] != x[b] ? x[a] < x[b] : y[a] > y[b];
}
void update(ll pos, ll val) {
for (; pos <= n; pos += pos & -pos) bit[pos] += val;
}
ll get(ll pos) {
ll res = 0;
for (; pos > 0; pos -= pos & -pos) res += bit[pos];
return res;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
id[i] = i;
ys[i] = y[i];
}
sort(ys + 1, ys + n + 1);
for (ll i = 1; i <= n; i++) r[i] = lower_bound(ys + 1, ys + n + 1, y[i]) - ys;
sort(id + 1, id + n + 1, cmp);
for (ll t = 1; t <= n; t++) {
ll i = id[t];
out[i] = (t - 1) - get(r[i] - 1);
update(r[i], 1);
}
for (ll i = 1; i <= n; i++) bit[i] = 0;
for (ll t = n; t >= 1; t--) {
ll i = id[t];
in[i] = get(r[i]);
update(r[i], 1);
}
for (ll i = 1; i <= n; i++) cout << in[i] << ' ';
cout << '\n';
for (ll i = 1; i <= n; i++) cout << out[i] << ' ';
cout << '\n';
}
5
1 10
2 5
3 4
6 9
1 7
4 1 0 0 2
0 2 3 1 1
.
Ví dụGiao Điểm Vuông Góc
Đề. đoạn ngang hoặc dọc, tọa độ trong , không đầu mút nào là giao điểm. Đếm số giao điểm giữa đoạn ngang và đoạn dọc.
Lời giải. Quét một đường thẳng đứng từ trái sang phải. Đoạn ngang từ tới “sống” khi đường quét nằm giữa hai đầu của nó: thêm vào Fenwick tại , gỡ tại . Đoạn dọc tại từ tới giao với đúng các đoạn ngang đang sống có trong , đếm bằng hai tổng tiền tố.
Bước 1: sự kiện. Mỗi sự kiện là (x, loại, y1, y2): loại 0 là thêm đoạn ngang, 1 là câu hỏi của đoạn dọc, 2 là gỡ đoạn ngang; sắp theo rồi theo loại. OFF dời từ sang chỉ số dương cho Fenwick.
if (y1 == y2) {
ev.push_back({x1, 0, y1 + OFF, 0});
ev.push_back({x2, 2, y1 + OFF, 0});
} else ev.push_back({x1, 1, y1 + OFF, y2 + OFF});
Bước 2: quét.
for (array<ll, 4> e : ev) {
if (e[1] == 0) update(e[2], 1);
else if (e[1] == 2) update(e[2], -1);
else ans += get(e[3]) - get(e[2] - 1);
}
Thứ tự cùng (thêm, hỏi, gỡ) sẽ đếm cả giao điểm ở đầu mút; đề bảo đảm không có, nên thứ tự nào cũng cho cùng đáp án.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll OFF = 1e6 + 1, SZ = 2e6 + 5;
ll n, bit[SZ];
vector<array<ll, 4>> ev;
void update(ll pos, ll val) {
for (; pos < SZ; pos += pos & -pos) bit[pos] += val;
}
ll get(ll pos) {
ll res = 0;
for (; pos > 0; pos -= pos & -pos) res += bit[pos];
return res;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) {
ll x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (y1 == y2) {
ev.push_back({x1, 0, y1 + OFF, 0});
ev.push_back({x2, 2, y1 + OFF, 0});
} else ev.push_back({x1, 1, y1 + OFF, y2 + OFF});
}
sort(ev.begin(), ev.end());
ll ans = 0;
for (array<ll, 4> e : ev) {
if (e[1] == 0) update(e[2], 1);
else if (e[1] == 2) update(e[2], -1);
else ans += get(e[3]) - get(e[2] - 1);
}
cout << ans << '\n';
}
5
0 2 6 2
1 5 5 5
2 0 2 6
4 1 4 4
7 0 7 3
3
với .
Câu hỏi ngoại tuyến theo ngưỡng
“Đếm phần tử lớn hơn trên đoạn ” có hai chiều: chỉ số và giá trị. Coi giá trị là trục quét: sắp phần tử và câu hỏi theo giá trị giảm dần; tới câu hỏi ngưỡng thì mọi phần tử lớn hơn đã được bật trong Fenwick theo chỉ số, đáp án là tổng đoạn . Mỗi phần tử bật một lần, tổng . Cùng kỹ thuật cho câu hỏi theo thời điểm, theo trọng số cạnh (thêm cạnh khi ngưỡng tới, như bài 23), theo độ cao.
Hỏi nhanh
Quét các phần tử theo giá trị giảm dần để trả lời câu hỏi “đếm phần tử lớn hơn trên ”. Khi tới một câu hỏi, những phần tử nào phải đang được bật trong Fenwick?
Fenwick theo chỉ số trả lời “bao nhiêu phần tử đang bật nằm trong ”. Để số đó đúng bằng số phần tử lớn hơn trong đoạn, tập đang bật phải là tập các phần tử lớn hơn , không phụ thuộc đoạn hỏi. Sắp câu hỏi theo giảm dần giữ cho tập này chỉ lớn dần.
Ví dụĐếm Phần Tử Vượt Ngưỡng
Đề. Mảng số, câu hỏi : đếm phần tử lớn hơn trong .
Lời giải. Trả lời ngoại tuyến: xếp câu hỏi theo giảm dần. Với ngưỡng , chỉ các phần tử lớn hơn được “bật”; ngưỡng giảm dần thì tập phần tử bật chỉ lớn thêm, không bao giờ phải tắt. Đáp án của câu hỏi là số phần tử đã bật trong , đếm bằng Fenwick theo vị trí.
Bước 1: hai thứ tự. ia là chỉ số phần tử sắp theo giá trị giảm, iq là chỉ số câu hỏi sắp theo giảm.
sort(ia + 1, ia + n + 1, ca);
sort(iq + 1, iq + q + 1, cq);
Bước 2: con trỏ trên phần tử. Con trỏ j chỉ tiến: trước mỗi câu hỏi, bật mọi phần tử có giá trị lớn hơn chưa bật. Đáp án ghi theo chỉ số câu hỏi gốc, cuối cùng in lại theo thứ tự đầu vào.
for (ll t = 1, j = 1; t <= q; t++) {
ll i = iq[t];
while (j <= n && a[ia[j]] > qk[i]) update(ia[j++]);
ans[i] = get(qr[i]) - get(ql[i] - 1);
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 3e4 + 5, Q = 2e5 + 5;
ll n, q, a[N], ia[N], bit[N], ql[Q], qr[Q], qk[Q], iq[Q], ans[Q];
bool ca(ll x, ll y) {
return a[x] > a[y];
}
bool cq(ll x, ll y) {
return qk[x] > qk[y];
}
void update(ll pos) {
for (; pos <= n; pos += pos & -pos) bit[pos]++;
}
ll get(ll pos) {
ll res = 0;
for (; pos > 0; pos -= pos & -pos) res += bit[pos];
return res;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
ia[i] = i;
}
cin >> q;
for (ll i = 1; i <= q; i++) {
cin >> ql[i] >> qr[i] >> qk[i];
iq[i] = i;
}
sort(ia + 1, ia + n + 1, ca);
sort(iq + 1, iq + q + 1, cq);
for (ll t = 1, j = 1; t <= q; t++) {
ll i = iq[t];
while (j <= n && a[ia[j]] > qk[i]) update(ia[j++]);
ans[i] = get(qr[i]) - get(ql[i] - 1);
}
for (ll i = 1; i <= q; i++) cout << ans[i] << '\n';
}
6
4 1 7 4 9 2
4
1 6 4
2 5 3
3 3 7
4 6 1
2
3
0
3
cộng hai lần sắp.
Diện tích hợp hình chữ nhật
Quét theo ; mỗi hình chữ nhật cho sự kiện trên khoảng tại và tại . Giữa hai sự kiện liên tiếp, diện tích phủ là (tổng độ dài các đang được phủ ít nhất một lần) nhân (khoảng cách hai sự kiện). Độ dài phủ giữ trong cây phân đoạn trên các khoảng cơ sở sau khi nén: mỗi nút lưu cnt là số lần nó được phủ trọn và len là độ dài phủ trong đoạn của nó. Nút có cnt > 0 thì phủ trọn, ngược lại len là tổng hai con. Không cần đẩy nhãn xuống, vì mỗi lần trừ luôn khớp với một lần cộng trước đó trên đúng các nút ấy.
Ví dụDiện Tích Hợp Hình Chữ Nhật
Đề. hình chữ nhật song song trục, tọa độ trong . Tính diện tích phần được ít nhất một hình phủ.
Lời giải. Quét theo . Giữa hai sự kiện liên tiếp, phần được phủ trên trục không đổi, nên diện tích của dải đó là độ dài phủ nhân bề rộng dải. Cần một cấu trúc giữ “tổng độ dài các khoảng đang được ít nhất một hình phủ” khi thêm và bớt khoảng: cây phân đoạn trên các tọa độ đã nén.
Bước 1: nén . Các thành dãy ys[1..k]; lá của cây là khoảng , nên cây có lá.
sort(ys + 1, ys + k + 1);
k = unique(ys + 1, ys + k + 1) - ys - 1;
Bước 2: độ dài phủ của một nút. cnt[id] đếm số hình phủ trọn đoạn của nút. Nút có cnt > 0 được phủ trọn, độ dài là cả khoảng của nó; ngược lại là tổng hai con (lá chưa phủ thì 0).
void pull(ll id, ll l, ll r) {
if (cnt[id]) len[id] = ys[r + 1] - ys[l];
else if (l == r) len[id] = 0;
else len[id] = len[id * 2] + len[id * 2 + 1];
}
Bước 3: thêm, bớt một khoảng. Cộng val vào cnt của các nút phủ trọn đoạn lá cần cập nhật, rồi pull ngược lên. Không đẩy nhãn xuống: mỗi lần trừ khớp đúng một lần cộng trước đó trên cùng các nút, nên cnt không bao giờ âm và len[1] luôn đúng.
void update(ll id, ll l, ll r, ll u, ll v, ll val) {
if (v < l || r < u) return;
if (u <= l && r <= v) {
cnt[id] += val;
pull(id, l, r);
return;
}
ll mid = (l + r) >> 1;
update(id * 2, l, mid, u, v, val);
update(id * 2 + 1, mid + 1, r, u, v, val);
pull(id, l, r);
}
Bước 4: quét sự kiện. Trước khi xử lý sự kiện tại , cộng len[1] nhân khoảng cách từ sự kiện trước. Khoảng từ tới ứng với các lá từ vị trí của tới ngay trước vị trí của .
for (array<ll, 4> e : ev) {
area += len[1] * (e[0] - px);
px = e[0];
ll l = lower_bound(ys + 1, ys + k + 1, e[2]) - ys;
ll r = lower_bound(ys + 1, ys + k + 1, e[3]) - ys - 1;
update(1, 1, k - 1, l, r, e[1]);
}
Diện tích tới , dùng ll.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 5;
ll n, k, ys[N], cnt[4 * N], len[4 * N];
vector<array<ll, 4>> ev;
void pull(ll id, ll l, ll r) {
if (cnt[id]) len[id] = ys[r + 1] - ys[l];
else if (l == r) len[id] = 0;
else len[id] = len[id * 2] + len[id * 2 + 1];
}
void update(ll id, ll l, ll r, ll u, ll v, ll val) {
if (v < l || r < u) return;
if (u <= l && r <= v) {
cnt[id] += val;
pull(id, l, r);
return;
}
ll mid = (l + r) >> 1;
update(id * 2, l, mid, u, v, val);
update(id * 2 + 1, mid + 1, r, u, v, val);
pull(id, l, r);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) {
ll x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
ev.push_back({x1, 1, y1, y2});
ev.push_back({x2, -1, y1, y2});
ys[++k] = y1;
ys[++k] = y2;
}
sort(ys + 1, ys + k + 1);
k = unique(ys + 1, ys + k + 1) - ys - 1;
sort(ev.begin(), ev.end());
ll area = 0, px = ev[0][0];
for (array<ll, 4> e : ev) {
area += len[1] * (e[0] - px);
px = e[0];
ll l = lower_bound(ys + 1, ys + k + 1, e[2]) - ys;
ll r = lower_bound(ys + 1, ys + k + 1, e[3]) - ys - 1;
update(1, 1, k - 1, l, r, e[1]);
}
cout << area << '\n';
}
3
0 0 4 2
2 1 5 4
5 0 7 1
17
.
Khi đi thi
- Đoạn, khoảng thời gian, hình chữ nhật: nghĩ ngay tới sự kiện mở và đóng. Viết rõ đoạn đóng hay nửa mở, rồi chọn hay và thứ tự khi trùng tọa độ cho đúng.
- Hai chiều: quét một chiều, giữ chiều kia trong Fenwick hay cây phân đoạn; tọa độ lớn thì nén trước.
- Câu hỏi có ngưỡng biết trước: sắp câu hỏi theo ngưỡng, thêm phần tử dần, trả lời ngoại tuyến.
Tóm tắt
- Đoạn là tại , tại ; quét sự kiện đã sắp cho mức phủ trên từng đoạn giữa hai sự kiện.
- Gán phòng: quét theo đầu, hàng đợi ưu tiên theo thời điểm rảnh, số phòng bằng mức phủ lớn nhất.
- Bao chứa và giao điểm: quét một chiều, Fenwick chiều còn lại.
- Ngưỡng ngoại tuyến: quét theo giá trị, bật dần phần tử, hỏi đoạn bằng Fenwick.
- Diện tích hợp: quét , cây phân đoạn với
cntvàlentrên đã nén.
Bài tập
multiset giờ rảnh của người, mỗi phim gán cho người rảnh muộn nhất mà vẫn kịp.set các điểm có cách điểm hiện tại không quá khoảng cách tốt nhất, theo ; chỉ xét điểm có trong dải .set vị trí đang chặn, người hỏi lấy vị trí nhỏ nhất.Đọc thêm
- Thuật toán đường quétVNOI WikiĐường quét trong hình học và bài trên đoạn, viết bằng tiếng Việt.
- Range Queries with Sweep LineUSACO GuideKết hợp đường quét với cấu trúc dữ liệu đoạn, kèm bài luyện.
- 扫描线OI WikiDiện tích hợp hình chữ nhật bằng cây phân đoạn.
- Search for a pair of intersecting segmentscp-algorithmsĐường quét kèm tập có thứ tự trong hình học.