Học Binary Indexed Trees từ các kĩ thuật đơn giản nhất Nguyễn Như Thắng GV THPT Chuyên Lào Cai 0 Mở đầu Bài toán cơ sở Cho dãy số nguyên , có m câu hỏi, mỗi câu hỏi yêu cầu tính tổng 1 đoạn liên tiếp[.]
Học Binary Indexed Trees từ kĩ thuật đơn giản Nguyễn Như Thắng- GV THPT Chuyên Lào Cai Mở đầu: Bài toán sở: Cho dãy số nguyên đoạn liên tiếp từ đến với , có m câu hỏi, câu hỏi yêu cầu tính tổng Với cách giải thông thường yêu cầu ta duyệt từ đến để tính tổng, độ phức tạp thuật toán O(m.n) Nếu m n cỡ 105 chắn thực thời gian cho phép Để giải vấn đề, ta cải tiến cách tính trước tổng cộng dồn s[i] tổng số từ đọc liệu vào thời gian chung O(n) Sau đó, câu hỏi ta thực thời gian O(1) cách lấy , độ phức tạp tốn cịn O(m+n) Tuy nhiên ta mở rộng tốn cách bổ sung thêm m yêu cầu đan xen vào m yêu cầu ban đầu là: cập nhật lại trị Khi đó, việc tính sẵn tổng cộng dồn s[i] lại trở nên vơ nghĩa Vì dãy số liên tục biến động, s[i] biến động, độ phức tạp tốn quay nguyên trạng ban đầu O(m.n) Tuy nhiên, ý tưởng việc cộng dồn lại không vô nghĩa ta kết hợp với số nhị phân để tạo giá trị cộng dồn quản lý số nhị phân (Binary Index Tree) Mỗi cần cập nhật, tính tổng đoạn, ta hồn tồn thực thời gian chung O(logN) Độ phức tạp toán O(NlogN+2MlogN) Cấu trúc liệu BIT có chất ý tưởng cộng dồn mã hóa đoạn cộng dồn quản lý số nhị phân Đây loại cấu trúc liệu để thuật toán thực nhanh Trong viết thảo luận cấu trúc liệu Binary Indexed Trees (cây nhị phân số) Theo Peter M Fenwick cấu trúc lần sử dụng để nén liệu Bây thường sử dụng để lưu trữ tần số thao tác với bảng tần số tích lũy u cầu tốn sở thay việc tìm min, max, skkn Giới thiệu toán Xét tốn sau Chúng ta có n hộp truy vấn là: Thêm số viên bi vào hộp i Tính số lượng viên bi từ hộp k tới hộp l Giải pháp đơn giản có độ phức tạp thời gian O(1) cho truy vấn O(n) cho truy vấn Giả sử thực m truy vấn Trường hợp xấu (khi tất truy vấn 2) có độ phức tạp thời gian O(n×m) Sử dụng cấu trúc segment tree, giải tốn với trường hợp xấu có độ phức tạp thời gian O(m.log2n) Một cách khác sử dụng cấu trúc Binary Indexed Trees, với phức tạp thời gian trường hợp xấu O(m.log2n), Binary Indexed Trees dễ viết mã u cầu khơng gian nhớ so với segment tree Tương ứng O(n) so với O(4n) Để hiểu BIT, bạn cần hiểu kĩ phần Mở đầu (nói cộng dồn), biết cách biểu diễn số nhị phân, phép toán logic bit Ký hiệu BIT - Binary Indexed Tree MaxVal - giá trị lớn tần số khác không f[i] - tần số (số lần xuất hiện) giá trị với số i, i = … MaxVal c[i] - tần số tích lũy cho số i (c[i] = f[1] + f[2] + + f[i]) tree[i] - tổng tần số f lưu trữ BIT với số i (phần sau mô tả số có nghĩa gì) Đơi viết tần số thay tổng tần số lưu trữ BIT - số bù số nguyên num (đảo ngược chữ số nhị phân num: 1, 0) Chú ý: Thông thường đặt f[0] = 0, c[0] = 0, tree[0] = 0, đơi ta bỏ qua số skkn Ý tưởng tảng Mỗi số nguyên biểu diễn tổng lũy thừa 2, hay biểu diễn hệ số Theo cách này, tần số tích lũy biểu diễn tổng tập tần số Trong toán này, tập chứa số liên tiếp tần số idx số BIT r vị trí chữ số cuối (chữ số bên phải nhất) biểu diễn nhị phân idx tree[idx] tổng tần số f từ số (idx - 2r + 1) tới số idx (xem bảng để hiểu rõ hơn) Chúng ta nói idx quản lí số từ (idx - 2r + 1) tới idx (chú ý việc quản lí chìa khóa thuật tốn cách thao tác với cây) Ví dụ: Theo số nhị phân: 12(10)=1100(2), nút 12 quản lý nút có số từ (=12-22+1) đến 12 Bảng cộng dồn tần số (tree) tổng giá trị nút quản lý: ví dụ: tree[12]=f[9]+f[10]+f[11]+f[12]; tree[14]=f[13]+f[14]; tree[1]=f[1]; tree[2]=f[1]+f[2]; tree[4]=f[1]+ f[2]+f[3]+f[4]; tree[5]=f[5]; tree[6]=f[5]+f[6];… Hãy quan sát bảng giá trị minh họa bên dưới: idx 10 11 12 13 14 15 16 f[idx] 1 2 c[idx] 1 8 12 14 19 21 23 26 27 27 29 tree[idx] 1 4 12 11 29 Bảng idx Các số mà idx quản lí 1 5 idx 10 11 12 13 14 15 16 Các số mà idx quản lí 9 10 11 12 13 13 14 15 16 Bảng - Bảng quản lí số Hình Cây quản lí số (cột hiển thị đoạn tần số tích lũy phần tử đầu) skkn Hình Cây tần số (tree) Giả sử tìm tần số tích lũy 13 phần tử sau: Trong biểu diễn nhị phân, 13 1101 Vì vậy, tính c[1101] = tree[1101] + tree[1100] + tree[1000] = + 11 + 12 = 26 Tổng đoạn từ đến 13 c[13]=tree[13]+tree[12]+tree[8] Tính tổng từ đoạn có số 12 đến 13 sau: c[13]-c[11]=tree[13]+tree[12]+tree[8]-(tree[11]+tree[10]+tree[8])=tree[13]+ tree[12]-tree[11]-tree[10]=3+11-2-7=5 f[12]+f[13]=2+3 Lấy chữ số bên phải Chữ số bên phải hay gọi chữ số cuối Để lấy số cuối đó, ta gọi num số nguyên ta cần lấy số cuối Giả sử num có dạng nhị phân a1b, a biểu diễn chữ số nhị phân trước số cuối b gồm chữ số khơng đằng sau số cuối (b=000 000nghịch đảo b (~b) = =111…111) Số bù số num ~num, ~num xác định cách nghịch đảo toàn bit biểu diễn num Số bù cách biểu diễn số đối số num Được xác định cách lấy số bù cộng thêm Vậy ta có: (num)= (~num)= (-num)= Phép tốn AND (num)&(-num)= ( (num)+(-num)=00…00 )&( ) = 010 0= (ở r vị trí số cuối cùng) Vậy ta dễ dàng lập số cuối phép toán (num&-num) C++ & -= 010 Tính tần số tích lũy Nếu muốn đọc tần số tích lũy số nguyên idx, cộng tree[idx] vào sum, sau loại bỏ bit cuối idx từ (tức thay đổi chữ số cuối không) lặp lại điều idx lớn Chúng ta sử dụng hàm sau (viết C++): skkn int read(int idx) { Vòng lặp int sum = 0; Vòng lặp while (idx > 0) { sum += tree[idx]; idx -= (idx & -idx); lặp Vịng } return sum; } Ví dụ với idx = 13, sum = 0: Vịng Vị trí số idx lặp idx & Tree[idx] cuối Sum -idx 13 = 1101 (20) 12 = 1100 11 (22) 14 = 1000 12 (23) 26 0=0 - - - - Vì vậy, kết tần số tích lũy số 13 26 Số lần lặp hàm số bit idx, số lần lặp nhiều log2MaxVal Độ phức tạp thời gian hàm read: O(log2MaxVal) Hình – Mũi tên minh họa đường từ số 13 tới việc tính sum skkn Cập nhật lại giá trị nút Để cập nhật giá trị nút f[idx] tác động lên tất nút quản lý nút tree[idx] Ví dụ: để cập nhật lại giá trị nút idx=9; ta cần cập nhật nút 10, 12,16,32,64,…2k ( kMaxVal Biểu diễn nút số nhị phân theo cập nhật nút theo thứ tự sau: Bắt đầu từ nút idx=9=100121010211002100002100000210000002… Hàm trong C++ cài đặt sau: Vòng lặp void update(int idx, int val) { while (idx