Đếm cặp thỏa ba bất đẳng thức cùng lúc là khó: sắp theo một tọa độ, Fenwick lo được tọa độ thứ hai, còn tọa độ thứ ba thì không. CDQ chia đôi theo tọa độ đầu tiên: mọi cặp hoặc nằm gọn trong một nửa (đệ quy xử lý), hoặc có ở nửa trái và ở nửa phải. Với các cặp vắt qua, tọa độ đầu đã tự thỏa, chỉ còn bài hai chiều giải bằng ghép theo tọa độ thứ hai và Fenwick trên tọa độ thứ ba. Tọa độ đầu thường là thời gian, biến bài có cập nhật thành bài tĩnh, ngoại tuyến.
Điểm bị trội ba chiều
Sắp các điểm theo . Hàm cdq(l, r):
- Gọi
cdq(l, mid)vàcdq(mid + 1, r); sau đó mỗi nửa đã được sắp theo (ghép như sắp xếp trộn). - Ghép hai nửa theo . Gặp phần tử trái thì cộng vào Fenwick tại vị trí ; gặp phần tử phải thì hỏi tổng tiền tố tới của nó. Mọi phần tử trái có không lớn hơn mọi phần tử phải, nên điều kiện còn lại đúng là và .
- Xóa các phần tử trái khỏi Fenwick bằng cách cộng ngược, không xóa cả mảng.
Mỗi tầng tốn , có tầng: .
Điểm trùng nhau. Hai điểm bằng nhau ở cả ba tọa độ trội lẫn nhau, nhưng CDQ chỉ đếm từ trái sang phải. Gộp các điểm trùng thành một phần tử có trọng số cnt, cộng cnt vào Fenwick; kết quả của một nhóm là số đếm được cộng cnt - 1 (các bản sao khác trong nhóm). Sau khi gộp, nếu với trái, phải thì đứng trước theo thứ tự từ điển, nên phần tử phải không thể trội phần tử trái và không cặp nào bị bỏ sót.
Hỏi nhanh
Vì sao phải gộp các điểm trùng hoàn toàn trước khi chạy CDQ?
CDQ chỉ đếm cặp trái sang phải. Hai bản sao chia về hai nửa thì bản bên trái không thấy bản bên phải dù chúng trội lẫn nhau. Gộp lại và cộng cnt - 1 cho đúng cả nhóm.
Ví dụPhổ Thống Trị Ba Chiều
Đề. điểm với tọa độ tới . Với mỗi , là số có cả ba tọa độ không lớn hơn. In số điểm có với mỗi .
Lời giải. Sắp các điểm theo và gộp các điểm trùng nhau thành một bản kèm số bản sao cnt. Khi đó nếu có cả ba tọa độ không lớn hơn và hai điểm khác nhau, thì đứng trước theo thứ tự từ điển, nên chỉ cần đếm các điểm đứng trước. CDQ chia đôi dãy: cặp nằm trong cùng một nửa do đệ quy lo; cặp ( ở nửa trái, ở nửa phải) đã chắc , chỉ còn so và : quét theo , Fenwick theo .
Bước 1: gộp điểm trùng. Sau khi sắp, điểm giống hệt điểm trước thì tăng cnt, ngược lại mở một bản mới. Không gộp thì hai bản sao của cùng một điểm có thể rơi vào hai nửa, và bản đứng sau sẽ không được tính cho bản đứng trước.
for (int i = 1; i <= n; i++) {
if (m && tmp[i].a == p[m].a && tmp[i].b == p[m].b && tmp[i].c == p[m].c) p[m].cnt++;
else p[++m] = tmp[i], p[m].cnt = 1, p[m].res = 0;
}
Bước 2: ghép hai nửa. Gọi hai nửa trước; khi trả về, mỗi nửa đã được sắp lại theo (nhờ bước trộn ở cuối hàm), nên quét được như trộn hai dãy. Với mỗi j ở nửa phải, đưa vào Fenwick mọi i ở nửa trái có , mỗi điểm cộng cnt của nó tại vị trí . get(p[j].c) là số điểm trái có và đều không lớn hơn.
int i = l, t = l;
for (int j = mid + 1; j <= r; j++) {
while (i <= mid && p[i].b <= p[j].b) add(p[i].c, p[i].cnt), tmp[t++] = p[i++];
p[j].res += get(p[j].c);
tmp[t++] = p[j];
}
Bước 3: hoàn tác và trộn. Trừ lại đúng các điểm đã cộng, không xóa cả mảng Fenwick. tmp là kết quả trộn hai nửa theo , chép lại vào p[l..r].
for (int x = l; x < i; x++) add(p[x].c, -p[x].cnt);
while (i <= mid) tmp[t++] = p[i++];
for (int x = l; x <= r; x++) p[x] = tmp[x];
Bước 4: kết quả. Một điểm có res điểm khác nằm dưới nó, cộng cnt - 1 bản sao của chính nó. Cả nhóm bản sao có cùng , nên cộng cnt vào ans tại chỉ số đó.
for (int i = 1; i <= m; i++) ans[p[i].res + p[i].cnt - 1] += p[i].cnt;
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5, K = 2e5 + 5;
struct P {
int a, b, c, cnt, res;
};
int n, k, m, bit[K], ans[N];
P p[N], tmp[N];
bool cmp(P x, P y) {
if (x.a != y.a) return x.a < y.a;
if (x.b != y.b) return x.b < y.b;
return x.c < y.c;
}
void add(int i, int v) {
for (; i <= k; i += i & -i) bit[i] += v;
}
int get(int i) {
int s = 0;
for (; i; i -= i & -i) s += bit[i];
return s;
}
void cdq(int l, int r) {
if (l == r) return;
int mid = (l + r) / 2;
cdq(l, mid);
cdq(mid + 1, r);
int i = l, t = l;
for (int j = mid + 1; j <= r; j++) {
while (i <= mid && p[i].b <= p[j].b) add(p[i].c, p[i].cnt), tmp[t++] = p[i++];
p[j].res += get(p[j].c);
tmp[t++] = p[j];
}
for (int x = l; x < i; x++) add(p[x].c, -p[x].cnt);
while (i <= mid) tmp[t++] = p[i++];
for (int x = l; x <= r; x++) p[x] = tmp[x];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> tmp[i].a >> tmp[i].b >> tmp[i].c;
sort(tmp + 1, tmp + n + 1, cmp);
for (int i = 1; i <= n; i++) {
if (m && tmp[i].a == p[m].a && tmp[i].b == p[m].b && tmp[i].c == p[m].c) p[m].cnt++;
else p[++m] = tmp[i], p[m].cnt = 1, p[m].res = 0;
}
cdq(1, m);
for (int i = 1; i <= m; i++) ans[p[i].res + p[i].cnt - 1] += p[i].cnt;
for (int d = 0; d < n; d++) cout << ans[d] << '\n';
}
5 3
1 1 1
1 1 1
2 2 2
2 1 3
3 3 3
0
2
2
0
1
Mỗi tầng đệ quy , có tầng: .
Thời gian làm chiều thứ nhất
Bài có cập nhật và truy vấn xen kẽ: truy vấn chỉ chịu ảnh hưởng của các cập nhật xảy ra trước. Coi chỉ số thao tác là tọa độ thứ nhất; dãy thao tác vốn đã sắp theo nó. Ở mỗi lần ghép, cập nhật nửa trái tác động lên truy vấn nửa phải, và bài còn lại là bài tĩnh: điểm có trọng số, hỏi tổng trong vùng.
Truy vấn hình chữ nhật tách thành bốn truy vấn góc có dấu theo bao hàm loại trừ, mỗi góc hỏi tổng các điểm có , . Khi ghép theo , cập nhật cùng phải đứng trước truy vấn. Fenwick theo có thể lớn tới cạnh bảng mà không cần nén, miễn mỗi lần chỉ hoàn tác những ô vừa cộng.
Ví dụTổng Trên Bảng Động
Đề. Bảng , . Tới lệnh cộng giá trị vào một ô và lệnh hỏi tổng hình chữ nhật, xen kẽ theo thời gian, kết thúc bằng lệnh 3.
Lời giải. Tổng hình chữ nhật bằng tổng bốn góc có dấu: , với là tổng các ô có tọa độ không lớn hơn. Mỗi lệnh hỏi thành bốn truy vấn góc. Dãy thao tác đã sắp theo thời gian, chính là chiều thứ nhất của CDQ: lệnh cộng ở nửa trái tác động lên truy vấn ở nửa phải. Trong một lần ghép, bài còn lại là bài tĩnh hai chiều: quét theo , hỏi Fenwick theo .
Bước 1: tách lệnh hỏi thành bốn góc. type = 0 là lệnh cộng v vào ô ; type = 1 là một góc của truy vấn id, với v là dấu .
q++;
op[++m] = {x2, y2, 1, q, 1};
op[++m] = {x1 - 1, y2, 1, q, -1};
op[++m] = {x2, y1 - 1, 1, q, -1};
op[++m] = {x1 - 1, y1 - 1, 1, q, 1};
Bước 2: ghép. Khung như ví dụ trước. Chỉ lệnh cộng được thêm vào Fenwick (và được hoàn tác); góc hỏi chỉ đọc. Điều kiện op[i].x <= op[j].x lấy cả bằng nhau, đúng nghĩa “không lớn hơn” của .
for (int j = mid + 1; j <= r; j++) {
while (i <= mid && op[i].x <= op[j].x) {
if (op[i].type == 0) add(op[i].y, op[i].v);
tmp[t++] = op[i++];
}
if (op[j].type == 1) ans[op[j].id] += op[j].v * get(op[j].y);
tmp[t++] = op[j];
}
Góc có hay : không lệnh cộng nào có , và get(0) trả 0, nên góc ấy đóng góp 0 như mong muốn. Fenwick dài tới mà không cần nén, vì mỗi lần ghép chỉ hoàn tác đúng các ô đã cộng.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5, W = 2e6 + 5;
struct Op {
int x, y, type, id;
ll v;
};
int w, m, q;
ll bit[W], ans[M];
Op op[M], tmp[M];
void add(int i, ll v) {
for (; i <= w; i += i & -i) bit[i] += v;
}
ll get(int i) {
ll s = 0;
for (; i > 0; i -= i & -i) s += bit[i];
return s;
}
void cdq(int l, int r) {
if (l == r) return;
int mid = (l + r) / 2;
cdq(l, mid);
cdq(mid + 1, r);
int i = l, t = l;
for (int j = mid + 1; j <= r; j++) {
while (i <= mid && op[i].x <= op[j].x) {
if (op[i].type == 0) add(op[i].y, op[i].v);
tmp[t++] = op[i++];
}
if (op[j].type == 1) ans[op[j].id] += op[j].v * get(op[j].y);
tmp[t++] = op[j];
}
for (int x = l; x < i; x++)
if (op[x].type == 0) add(op[x].y, -op[x].v);
while (i <= mid) tmp[t++] = op[i++];
for (int x = l; x <= r; x++) op[x] = tmp[x];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int type;
cin >> type >> w;
while (cin >> type && type != 3) {
if (type == 1) {
int x, y;
ll a;
cin >> x >> y >> a;
op[++m] = {x, y, 0, 0, a};
} else {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
q++;
op[++m] = {x2, y2, 1, q, 1};
op[++m] = {x1 - 1, y2, 1, q, -1};
op[++m] = {x2, y1 - 1, 1, q, -1};
op[++m] = {x1 - 1, y1 - 1, 1, q, 1};
}
}
if (m) cdq(1, m);
for (int i = 1; i <= q; i++) cout << ans[i] << '\n';
}
0 5
1 2 2 3
1 4 3 5
2 1 1 3 3
1 3 3 2
2 2 2 4 3
3
3
10
với là tổng số lệnh và góc.
Đếm theo thời gian xóa
Bài xóa dần phần tử và hỏi số nghịch thế trước mỗi lần xóa: đáp án trước lần xóa thứ bằng tổng nghịch thế ban đầu trừ đi số nghịch thế đã mất ở lần xóa đầu. Lần xóa phần tử làm mất các cặp nghịch thế giữa và những phần tử còn lại lúc đó, tức những phần tử bị xóa sau hoặc không bao giờ bị xóa.
Vậy mỗi phần tử có ba tọa độ: thời điểm xóa (không bị xóa thì ), vị trí , giá trị . Sắp theo giảm dần, với mỗi đếm các phần tử đứng trước (bị xóa muộn hơn) có hoặc . Hai trường hợp là hai lượt quét trong cùng một lần ghép: quét tăng cho trường hợp đầu, giảm cho trường hợp sau.
Ví dụNghịch Thế Khi Xóa
Đề. Hoán vị độ dài , xóa lần lượt giá trị. In số nghịch thế ngay trước mỗi lần xóa.
Lời giải. Gọi là thời điểm xóa phần tử ( nếu không bị xóa). Lần xóa làm mất đúng các cặp nghịch thế giữa và những phần tử còn lại lúc đó, tức các phần tử có . Sắp phần tử theo giảm dần, đếm cho mỗi số phần tử đứng trước nó tạo nghịch thế với nó: vị trí nhỏ hơn mà giá trị lớn hơn, hoặc vị trí lớn hơn mà giá trị nhỏ hơn. Đó là bài đếm cặp ba chiều , giải bằng CDQ như ví dụ đầu.
Bước 1: nghịch thế ban đầu. Fenwick đếm total, rồi trừ lại để Fenwick về 0. where[v] là vị trí của giá trị , dùng để ghi tim cho phần tử bị xóa. Sau đó id sắp theo thời điểm xóa giảm dần; phần tử không bị xóa đứng đầu, và chúng không bao giờ cần lost.
total += i - 1 - get(a[i]);
add(a[i], 1);
Bước 2: lượt thứ nhất của ghép. Hai nửa đã sắp theo vị trí. Quét j ở nửa phải theo vị trí tăng, đưa vào Fenwick các phần tử trái có vị trí nhỏ hơn. (i - l) - get(a[id[j]]) là số phần tử đó có giá trị lớn hơn.
for (int j = mid + 1; j <= r; j++) {
while (i <= mid && pos[id[i]] < pos[id[j]]) add(a[id[i]], 1), i++;
lost[id[j]] += (i - l) - get(a[id[j]]);
}
Bước 3: lượt thứ hai. Quét ngược, đưa vào các phần tử trái có vị trí lớn hơn; get(a[id[j]] - 1) đếm những phần tử có giá trị nhỏ hơn. Sau mỗi lượt hoàn tác Fenwick, rồi merge hai nửa theo vị trí để tầng trên quét được.
for (int j = r; j > mid; j--) {
while (i >= l && pos[id[i]] > pos[id[j]]) add(a[id[i]], 1), i--;
lost[id[j]] += get(a[id[j]] - 1);
}
Bước 4: in. Trước lần xóa thứ in total, rồi trừ lost của phần tử vừa xóa.
for (int k = 1; k <= m; k++) {
cout << total << '\n';
total -= lost[where[del[k]]];
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
int n, m, a[N], pos[N], tim[N], id[N], tmp[N], bit[N];
ll lost[N];
void add(int i, int v) {
for (; i <= n; i += i & -i) bit[i] += v;
}
int get(int i) {
int s = 0;
for (; i; i -= i & -i) s += bit[i];
return s;
}
bool byTime(int x, int y) {
return tim[x] > tim[y];
}
bool byPos(int x, int y) {
return pos[x] < pos[y];
}
void cdq(int l, int r) {
if (l == r) return;
int mid = (l + r) / 2;
cdq(l, mid);
cdq(mid + 1, r);
int i = l;
for (int j = mid + 1; j <= r; j++) {
while (i <= mid && pos[id[i]] < pos[id[j]]) add(a[id[i]], 1), i++;
lost[id[j]] += (i - l) - get(a[id[j]]);
}
for (int x = l; x < i; x++) add(a[id[x]], -1);
i = mid;
for (int j = r; j > mid; j--) {
while (i >= l && pos[id[i]] > pos[id[j]]) add(a[id[i]], 1), i--;
lost[id[j]] += get(a[id[j]] - 1);
}
for (int x = mid; x > i; x--) add(a[id[x]], -1);
merge(id + l, id + mid + 1, id + mid + 1, id + r + 1, tmp + l, byPos);
for (int x = l; x <= r; x++) id[x] = tmp[x];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
ll total = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
pos[i] = i, tim[i] = m + 1, id[i] = i;
total += i - 1 - get(a[i]);
add(a[i], 1);
}
for (int i = 1; i <= n; i++) add(a[i], -1);
vector<int> where(n + 1), del(m + 1);
for (int i = 1; i <= n; i++) where[a[i]] = i;
for (int k = 1; k <= m; k++) {
cin >> del[k];
tim[where[del[k]]] = k;
}
stable_sort(id + 1, id + n + 1, byTime);
cdq(1, n);
for (int k = 1; k <= m; k++) {
cout << total << '\n';
total -= lost[where[del[k]]];
}
}
5 3
3 1 5 2 4
5
1
3
4
2
1
.
CDQ cho quy hoạch động
Khi phụ thuộc mọi đứng trước thỏa vài bất đẳng thức (chuỗi dài nhất nhiều chiều), thứ tự gọi phải đổi: cdq(l, mid) xong hẳn, chuyển đóng góp từ nửa trái sang nửa phải, rồi mới gọi cdq(mid + 1, r). Lúc chuyển, mọi của nửa trái đã cuối cùng; nửa phải nhận thêm đóng góp trước khi tự đệ quy.
Vì nửa phải còn phải đệ quy theo thứ tự tọa độ đầu, không được trộn mảng gốc theo tọa độ thứ hai. Sắp hai danh sách chỉ số riêng cho lần chuyển, mỗi tầng , tổng . Fenwick lưu cặp (độ dài tốt nhất, số cách) và ghép bằng “lấy độ dài lớn hơn, bằng nhau thì cộng số cách”.
Hỏi nhanh
Trong CDQ cho DP, vì sao không gọi cả hai nửa trước rồi mới chuyển đóng góp như khi đếm cặp?
Phần tử ở nửa phải truyền tiếp cho các phần tử sau nó trong cùng nửa. Nếu chưa nhận đóng góp từ nửa trái thì các giá trị truyền tiếp đều sai.
Ví dụChuỗi Điểm Ba Chiều
Đề. test, mỗi test điểm ba chiều khác nhau. Tìm độ dài lớn nhất của chuỗi có cả ba tọa độ không giảm, và số tập điểm đạt độ dài đó theo mô-đun .
Lời giải. là độ dài chuỗi dài nhất kết thúc ở điểm , là số chuỗi đạt độ dài đó. Sắp theo ; các điểm khác nhau nên bước trước của (cả ba tọa độ không lớn hơn) luôn đứng trước . CDQ cho quy hoạch động phải đổi thứ tự gọi: xử lý xong hẳn nửa trái, chuyển đóng góp từ nửa trái sang nửa phải, rồi mới đệ quy nửa phải. Lúc chuyển, mọi bên trái đã cuối cùng, nên giá trị truyền đi là đúng.
Bước 1: Fenwick giữ cặp. Mỗi ô giữ (độ dài tốt nhất, số cách), gộp theo luật “độ dài lớn hơn thì thay, bằng nhau thì cộng số cách”. Phép gộp này kết hợp và giao hoán, nên dùng được trong Fenwick. & MASK là lấy dư cho . clear đặt lại đúng các ô đã đụng.
void upd(int p, int l, ll c) {
for (; p <= m; p += p & -p) {
if (l > bl[p]) bl[p] = l, bc[p] = c;
else if (l == bl[p]) bc[p] = (bc[p] + c) & MASK;
}
}
Bước 2: thứ tự gọi. cdq(l, mid) trước. Rồi chép chỉ số hai nửa vào L, R và sắp theo ; không sắp thẳng ord, vì nửa phải còn phải đệ quy theo thứ tự .
cdq(l, mid);
vector<int> L(ord + l, ord + mid + 1), R(ord + mid + 1, ord + r + 1);
sort(L.begin(), L.end(), byY);
sort(R.begin(), R.end(), byY);
Bước 3: chuyển đóng góp. Quét R theo tăng, đưa vào Fenwick các điểm trái có không lớn hơn, rồi hỏi tiền tố tới (đã nén): được bước trước tốt nhất ở nửa trái.
for (int j : R) {
while (i < (int)L.size() && y[L[i]] <= y[j]) upd(z[L[i]], len[L[i]], cnt[L[i]]), i++;
int bestL;
ll bestC;
ask(z[j], bestL, bestC);
if (bestL + 1 > len[j]) len[j] = bestL + 1, cnt[j] = bestC;
else if (bestL + 1 == len[j]) cnt[j] = (cnt[j] + bestC) & MASK;
}
for (int k = 0; k < i; k++) clear(z[L[k]]);
cdq(mid + 1, r);
Cuối cùng mới cdq(mid + 1, r): các điểm nửa phải đã nhận đủ đóng góp từ nửa trái trước khi truyền tiếp cho nhau.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
const ll MASK = (1 << 30) - 1;
int n, m, x[N], y[N], z[N], ord[N], len[N], bl[N];
ll cnt[N], bc[N];
bool byXYZ(int i, int j) {
if (x[i] != x[j]) return x[i] < x[j];
if (y[i] != y[j]) return y[i] < y[j];
return z[i] < z[j];
}
bool byY(int i, int j) {
return y[i] < y[j];
}
void upd(int p, int l, ll c) {
for (; p <= m; p += p & -p) {
if (l > bl[p]) bl[p] = l, bc[p] = c;
else if (l == bl[p]) bc[p] = (bc[p] + c) & MASK;
}
}
void ask(int p, int &l, ll &c) {
l = 0, c = 0;
for (; p; p -= p & -p) {
if (bl[p] > l) l = bl[p], c = bc[p];
else if (bl[p] == l) c = (c + bc[p]) & MASK;
}
}
void clear(int p) {
for (; p <= m; p += p & -p) bl[p] = 0, bc[p] = 0;
}
void cdq(int l, int r) {
if (l == r) return;
int mid = (l + r) / 2;
cdq(l, mid);
vector<int> L(ord + l, ord + mid + 1), R(ord + mid + 1, ord + r + 1);
sort(L.begin(), L.end(), byY);
sort(R.begin(), R.end(), byY);
int i = 0;
for (int j : R) {
while (i < (int)L.size() && y[L[i]] <= y[j]) upd(z[L[i]], len[L[i]], cnt[L[i]]), i++;
int bestL;
ll bestC;
ask(z[j], bestL, bestC);
if (bestL + 1 > len[j]) len[j] = bestL + 1, cnt[j] = bestC;
else if (bestL + 1 == len[j]) cnt[j] = (cnt[j] + bestC) & MASK;
}
for (int k = 0; k < i; k++) clear(z[L[k]]);
cdq(mid + 1, r);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
while (T--) {
cin >> n;
vector<int> zs;
for (int i = 1; i <= n; i++) cin >> x[i] >> y[i] >> z[i], zs.push_back(z[i]), ord[i] = i;
sort(zs.begin(), zs.end());
zs.erase(unique(zs.begin(), zs.end()), zs.end());
m = zs.size();
for (int i = 1; i <= n; i++) {
z[i] = lower_bound(zs.begin(), zs.end(), z[i]) - zs.begin() + 1;
len[i] = 1, cnt[i] = 1;
}
sort(ord + 1, ord + n + 1, byXYZ);
cdq(1, n);
int best = 0;
ll ways = 0;
for (int i = 1; i <= n; i++) {
if (len[i] > best) best = len[i], ways = cnt[i];
else if (len[i] == best) ways = (ways + cnt[i]) & MASK;
}
cout << best << ' ' << ways << '\n';
}
}
1
4
1 1 1
2 2 2
2 3 1
3 3 3
3 2
Mỗi tầng sắp hai danh sách và dùng Fenwick, đều : tổng .
Khi đi thi
- Đếm cặp hay tổng đóng góp với ba điều kiện thứ tự (hoặc hai điều kiện cộng thời gian): CDQ chia theo chiều đầu, ghép theo chiều thứ hai, Fenwick chiều thứ ba, .
- Kiểm kỹ điểm trùng và dấu bằng: gộp bản sao, đặt cập nhật trước truy vấn khi bằng tọa độ, hoàn tác Fenwick đúng phần đã cộng.
- DP nhiều chiều: trái, chuyển, rồi mới phải; sắp bản sao chỉ số để không phá thứ tự của nửa phải.
Tóm tắt
- Mọi cặp hoặc nằm trong một nửa, hoặc vắt từ trái sang phải; CDQ xử lý loại sau ở mỗi tầng.
- Thời gian làm chiều thứ nhất biến cập nhật xen truy vấn thành bài tĩnh ngoại tuyến.
- Bài xóa dần: gán thời điểm xóa, đếm đóng góp mất đi rồi trừ dần khỏi tổng.
- CDQ cho DP: nửa trái xong hẳn trước khi truyền sang nửa phải.
Bài tập
Đọc thêm
- Chia để trịVNOI WikiNền tảng chia để trị và đếm cặp vắt qua hai nửa.
- CDQ 分治OI WikiĐiểm bị trội ba chiều, chia theo thời gian và CDQ cho DP.
- Cây chỉ số nhị phânVNOI WikiFenwick dùng ở chiều cuối, bằng tiếng Việt.